Esempio n. 1
0
def test_lock_packages_with_null_description(locker: Locker, root: ProjectPackage):
    package_a = get_package("A", "1.0.0")
    package_a.description = None

    locker.set_lock_data(root, [package_a])

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"

[metadata.files]
A = []
"""

    assert content == expected
Esempio n. 2
0
def test_extras_dependencies_are_ordered(locker: Locker, root: ProjectPackage):
    package_a = get_package("A", "1.0.0")
    package_a.add_dependency(
        Factory.create_dependency(
            "B", {"version": "^1.0.0", "optional": True, "extras": ["c", "a", "b"]}
        )
    )
    package_a.requires[-1].activate()

    locker.set_lock_data(root, [package_a])

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
B = {version = "^1.0.0", extras = ["a", "b", "c"], optional = true}

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"

[metadata.files]
A = []
"""

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    assert content == expected
Esempio n. 3
0
def test_extras_dependencies_are_ordered(locker: Locker, root: ProjectPackage):
    package_a = get_package("A", "1.0.0")
    package_a.add_dependency(
        Factory.create_dependency(
            "B", {"version": "^1.0.0", "optional": True, "extras": ["c", "a", "b"]}
        )
    )
    package_a.requires[-1].activate()

    locker.set_lock_data(root, [package_a])

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
B = {version = "^1.0.0", extras = ["a", "b", "c"], optional = true}

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"

[metadata.files]
A = []
"""

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    assert content == expected
Esempio n. 4
0
def test_lock_packages_with_null_description(locker: Locker, root: ProjectPackage):
    package_a = get_package("A", "1.0.0")
    package_a.description = None

    locker.set_lock_data(root, [package_a])

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"

[metadata.files]
A = []
"""

    assert content == expected
Esempio n. 5
0
def save(locker: Locker, lock_data: _TOMLDocument, root: Package) -> None:
    """Validate the lock data and write it to disk.

    Args:
        locker: The locker object.
        lock_data: The lock data.
        root: The root package of the Poetry project.
    """
    packages = load_packages(locker, lock_data)
    locker.set_lock_data(root, packages)
Esempio n. 6
0
def push(hub, args):
    # TODO: local imports to avoid circular dependencies, can clean-up once Poetry's plugin system arrives
    from poetry.packages.locker import Locker
    from tomlkit.toml_file import TOMLFile

    current_index = hub.current.index
    root_url = hub.current.root_url.url
    current_user, current_index = current_index[len(root_url):].split('/')
    available_indices = hub.http_api("get",
                                     root_url,
                                     quiet=True,
                                     check_version=False).result

    local_config = TOMLFile('pyproject.toml').read()
    locker = Locker('poetry.lock', local_config)
    locked_repository = locker.locked_repository(with_dev_reqs=not args.no_dev)

    for pkg in locked_repository.packages:
        name, version = pkg.name, pkg.version.text
        if pkg.source_url and pkg.source_url != current_index:
            hub.warn(
                "'{}=={}' is locked from {} which is not the current index, skipping."
                .format(name, version, pkg.source_url))
            continue

        # try to guess the index from the download link
        project_url = hub.current.get_project_url(name)
        reply = hub.http_api("get", project_url, type="projectconfig")
        link = reply.result.get(version, {}).get('+links', [{}])[0].get('href')

        if not link.startswith(root_url):
            hub.warn(
                "'{}=={}' is mirrored from an external url, skipping.".format(
                    name, version))
            continue

        user, index, _ = link[len(root_url):].split('/', 2)
        if ((user, index) != (current_user, current_index)
                and not args.include_local_bases
                and available_indices.get(user, {}).get('indexes', {}).get(
                    index, {}).get('type') != 'mirror'):
            hub.info(
                "Skipping '{}=={}' available from local base '{}/{}'".format(
                    name, version, user, index))
            continue

        pkg_args = Namespace(pkgspec='{}=={}'.format(name, version),
                             index='{}/{}'.format(user, index),
                             **vars(args))
        devpi_push(hub, pkg_args)
Esempio n. 7
0
def load_packages(locker: Locker, lock_data: _TOMLDocument) -> List[Package]:
    """Load the packages from a TOML document with lock data.

    Args:
        locker: The locker object.
        lock_data: The lock data.

    Returns:
        The list of packages.
    """
    locker._lock_data = lock_data
    repository = locker.locked_repository(with_dev_reqs=True)
    activate_dependencies(repository.packages)
    return repository.packages  # type: ignore[no-any-return]  # noqa: F723
Esempio n. 8
0
    def create_poetry(
        self,
        cwd: Optional[Path] = None,
        io: Optional[IO] = None,
        disable_plugins: bool = False,
    ) -> Poetry:
        if io is None:
            io = NullIO()

        base_poetry = super(Factory, self).create_poetry(cwd)

        locker = Locker(
            base_poetry.file.parent / "poetry.lock", base_poetry.local_config
        )

        # Loading global configuration
        config = self.create_config(io)

        # Loading local configuration
        local_config_file = TOMLFile(base_poetry.file.parent / "poetry.toml")
        if local_config_file.exists():
            if io.is_debug():
                io.write_line(
                    "Loading configuration file {}".format(local_config_file.path)
                )

            config.merge(local_config_file.read())

        # Load local sources
        repositories = {}
        existing_repositories = config.get("repositories", {})
        for source in base_poetry.pyproject.poetry_config.get("source", []):
            name = source.get("name")
            url = source.get("url")
            if name and url:
                if name not in existing_repositories:
                    repositories[name] = {"url": url}

        config.merge({"repositories": repositories})

        poetry = Poetry(
            base_poetry.file.path,
            base_poetry.local_config,
            base_poetry.package,
            locker,
            config,
        )

        # Configuring sources
        self.configure_sources(
            poetry, poetry.local_config.get("source", []), config, io
        )

        plugin_manager = PluginManager("plugin", disable_plugins=disable_plugins)
        plugin_manager.load_plugins()
        poetry.set_plugin_manager(plugin_manager)
        plugin_manager.activate(poetry, io)

        return poetry
Esempio n. 9
0
def test_lock_file_should_not_have_mixed_types(locker: Locker,
                                               root: ProjectPackage):
    package_a = get_package("A", "1.0.0")
    package_a.add_dependency(Factory.create_dependency("B", "^1.0.0"))
    package_a.add_dependency(
        Factory.create_dependency("B", {
            "version": ">=1.0.0",
            "optional": True
        }))
    package_a.requires[-1].activate()
    package_a.extras["foo"] = [get_dependency("B", ">=1.0.0")]

    locker.set_lock_data(root, [package_a])

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
B = [
    {version = "^1.0.0"},
    {version = ">=1.0.0", optional = true},
]

[package.extras]
foo = ["B (>=1.0.0)"]

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"

[metadata.files]
A = []
"""

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    assert content == expected
Esempio n. 10
0
def test_locked_repository_uses_root_dir_of_package(
    locker: Locker, mocker: MockerFixture
):
    content = """\
[[package]]
name = "lib-a"
version = "0.1.0"
description = ""
category = "main"
optional = false
python-versions = "^2.7.9"
develop = true

[package.dependencies]
lib-b = {path = "../libB", develop = true}

[package.source]
type = "directory"
url = "lib/libA"

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"

[metadata.files]
lib-a = []
lib-b = []
"""

    locker.lock.write(tomlkit.parse(content))
    create_dependency_patch = mocker.patch(
        "poetry.factory.Factory.create_dependency", autospec=True
    )
    locker.locked_repository()

    create_dependency_patch.assert_called_once_with(
        "lib-b", {"develop": True, "path": "../libB"}, root_dir=mocker.ANY
    )
    call_kwargs = create_dependency_patch.call_args[1]
    root_dir = call_kwargs["root_dir"]
    assert root_dir.match("*/lib/libA")
    # relative_to raises an exception if not relative - is_relative_to comes in py3.9
    assert root_dir.relative_to(locker.lock.path.parent.resolve()) is not None
Esempio n. 11
0
def test_lock_file_should_not_have_mixed_types(locker: Locker,
                                               root: ProjectPackage):
    package_a = get_package("A", "1.0.0")
    package_a.add_dependency(Factory.create_dependency("B", "^1.0.0"))
    package_a.add_dependency(
        Factory.create_dependency("B", {
            "version": ">=1.0.0",
            "optional": True
        }))
    package_a.requires[-1].activate()
    package_a.extras["foo"] = [get_dependency("B", ">=1.0.0")]

    locker.set_lock_data(root, [package_a])

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
B = [
    {version = "^1.0.0"},
    {version = ">=1.0.0", optional = true},
]

[package.extras]
foo = ["B (>=1.0.0)"]

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"

[metadata.files]
A = []
"""

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    assert content == expected
Esempio n. 12
0
def poetry(tmp_dir: str, config: Config) -> Poetry:
    poetry = Poetry(
        CWD / "pyproject.toml",
        {},
        ProjectPackage("simple-project", "1.2.3"),
        Locker(CWD / "poetry.lock", {}),
        config,
    )

    return poetry
Esempio n. 13
0
def test_content_hash_with_legacy_is_compatible(
    local_config: dict[str, list[str]], fresh: bool, locker: Locker
) -> None:
    # old hash generation
    relevant_content = {}
    for key in locker._legacy_keys:
        relevant_content[key] = local_config.get(key)

    locker = locker.__class__(
        lock=locker.lock.path,
        local_config=local_config,
    )

    old_content_hash = sha256(
        json.dumps(relevant_content, sort_keys=True).encode()
    ).hexdigest()
    content_hash = locker._get_content_hash()

    assert (content_hash == old_content_hash) or fresh
Esempio n. 14
0
def test_locking_legacy_repository_package_should_include_source_section(
    root: ProjectPackage, locker: Locker
):
    package_a = Package(
        "A",
        "1.0.0",
        source_type="legacy",
        source_url="https://foo.bar",
        source_reference="legacy",
    )
    packages = [package_a]

    locker.set_lock_data(root, packages)

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.source]
type = "legacy"
url = "https://foo.bar"
reference = "legacy"

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"

[metadata.files]
A = []
"""

    assert content == expected
Esempio n. 15
0
def test_locking_legacy_repository_package_should_include_source_section(
    root: ProjectPackage, locker: Locker
):
    package_a = Package(
        "A",
        "1.0.0",
        source_type="legacy",
        source_url="https://foo.bar",
        source_reference="legacy",
    )
    packages = [package_a]

    locker.set_lock_data(root, packages)

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.source]
type = "legacy"
url = "https://foo.bar"
reference = "legacy"

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"

[metadata.files]
A = []
"""

    assert content == expected
Esempio n. 16
0
def test_locker_properly_loads_extras_legacy(locker: Locker):
    content = """\
[[package]]
name = "a"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
b = {version = "^1.0", optional = true}

[package.extras]
b = ["b (^1.0)"]

[[package]]
name = "b"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[metadata]
python-versions = "*"
lock-version = "1.1"
content-hash = "123456789"

[metadata.files]
"a" = []
"b" = []
"""

    locker.lock.write(tomlkit.parse(content))

    repository = locker.locked_repository()
    assert len(repository.packages) == 2

    packages = repository.find_packages(get_dependency("a", "1.0"))
    assert len(packages) == 1

    package = packages[0]
    assert len(package.requires) == 1
    assert len(package.extras) == 1

    dependency_b = package.extras["b"][0]
    assert dependency_b.name == "b"
Esempio n. 17
0
def test_locker_properly_loads_extras(locker: Locker):
    content = """\
[[package]]
name = "cachecontrol"
version = "0.12.5"
description = "httplib2 caching for requests"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"

[package.dependencies]
msgpack = "*"
requests = "*"

[package.dependencies.lockfile]
optional = true
version = ">=0.9"

[package.extras]
filecache = ["lockfile (>=0.9)"]
redis = ["redis (>=2.10.5)"]

[metadata]
lock-version = "1.1"
python-versions = "~2.7 || ^3.4"
content-hash = "c3d07fca33fba542ef2b2a4d75bf5b48d892d21a830e2ad9c952ba5123a52f77"

[metadata.files]
cachecontrol = []
"""

    locker.lock.write(tomlkit.parse(content))

    packages = locker.locked_repository().packages

    assert len(packages) == 1

    package = packages[0]
    assert len(package.requires) == 3
    assert len(package.extras) == 2

    lockfile_dep = package.extras["filecache"][0]
    assert lockfile_dep.name == "lockfile"
Esempio n. 18
0
def locker():
    with tempfile.NamedTemporaryFile() as f:
        f.close()
        locker = Locker(f.name, {})

        return locker
Esempio n. 19
0
def test_lock_file_data_is_ordered(locker: Locker, root: ProjectPackage):
    package_a = get_package("A", "1.0.0")
    package_a.add_dependency(Factory.create_dependency("B", "^1.0"))
    package_a.files = [{"file": "foo", "hash": "456"}, {"file": "bar", "hash": "123"}]
    package_git = Package(
        "git-package",
        "1.2.3",
        source_type="git",
        source_url="https://github.com/python-poetry/poetry.git",
        source_reference="develop",
        source_resolved_reference="123456",
    )
    packages = [package_a, get_package("B", "1.2"), package_git]

    locker.set_lock_data(root, packages)

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
B = "^1.0"

[[package]]
name = "B"
version = "1.2"
description = ""
category = "main"
optional = false
python-versions = "*"

[[package]]
name = "git-package"
version = "1.2.3"
description = ""
category = "main"
optional = false
python-versions = "*"
develop = false

[package.source]
type = "git"
url = "https://github.com/python-poetry/poetry.git"
reference = "develop"
resolved_reference = "123456"

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "178f2cd01dc40e96be23a4a0ae1094816626346346618335e5ff4f0b2c0c5831"

[metadata.files]
A = [
    {file = "bar", hash = "123"},
    {file = "foo", hash = "456"},
]
B = []
git-package = []
"""

    assert content == expected
Esempio n. 20
0
def test_locker_dumps_dependency_information_correctly(
    locker: Locker, root: ProjectPackage
):
    root_dir = Path(__file__).parent.parent.joinpath("fixtures")
    package_a = get_package("A", "1.0.0")
    package_a.add_dependency(
        Factory.create_dependency(
            "B", {"path": "project_with_extras", "develop": True}, root_dir=root_dir
        )
    )
    package_a.add_dependency(
        Factory.create_dependency(
            "C",
            {"path": "directory/project_with_transitive_directory_dependencies"},
            root_dir=root_dir,
        )
    )
    package_a.add_dependency(
        Factory.create_dependency(
            "D", {"path": "distributions/demo-0.1.0.tar.gz"}, root_dir=root_dir
        )
    )
    package_a.add_dependency(
        Factory.create_dependency(
            "E", {"url": "https://python-poetry.org/poetry-1.2.0.tar.gz"}
        )
    )
    package_a.add_dependency(
        Factory.create_dependency(
            "F", {"git": "https://github.com/python-poetry/poetry.git", "branch": "foo"}
        )
    )

    packages = [package_a]

    locker.set_lock_data(root, packages)

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    expected = """[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
B = {path = "project_with_extras", develop = true}
C = {path = "directory/project_with_transitive_directory_dependencies"}
D = {path = "distributions/demo-0.1.0.tar.gz"}
E = {url = "https://python-poetry.org/poetry-1.2.0.tar.gz"}
F = {git = "https://github.com/python-poetry/poetry.git", branch = "foo"}

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"

[metadata.files]
A = []
"""

    assert content == expected
Esempio n. 21
0
def test_lock_file_data_is_ordered(locker: Locker, root: ProjectPackage):
    package_a = get_package("A", "1.0.0")
    package_a.add_dependency(Factory.create_dependency("B", "^1.0"))
    package_a.files = [{"file": "foo", "hash": "456"}, {"file": "bar", "hash": "123"}]
    package_a2 = get_package("A", "2.0.0")
    package_a2.files = [{"file": "baz", "hash": "345"}]
    package_git = Package(
        "git-package",
        "1.2.3",
        source_type="git",
        source_url="https://github.com/python-poetry/poetry.git",
        source_reference="develop",
        source_resolved_reference="123456",
    )
    package_url_linux = Package(
        "url-package",
        "1.0",
        source_type="url",
        source_url="https://example.org/url-package-1.0-cp39-manylinux_2_17_x86_64.whl",
    )
    package_url_win32 = Package(
        "url-package",
        "1.0",
        source_type="url",
        source_url="https://example.org/url-package-1.0-cp39-win_amd64.whl",
    )
    packages = [
        package_a2,
        package_a,
        get_package("B", "1.2"),
        package_git,
        package_url_win32,
        package_url_linux,
    ]

    locker.set_lock_data(root, packages)

    with locker.lock.open(encoding="utf-8") as f:
        content = f.read()

    expected = """\
[[package]]
name = "A"
version = "1.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
B = "^1.0"

[[package]]
name = "A"
version = "2.0.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[[package]]
name = "B"
version = "1.2"
description = ""
category = "main"
optional = false
python-versions = "*"

[[package]]
name = "git-package"
version = "1.2.3"
description = ""
category = "main"
optional = false
python-versions = "*"
develop = false

[package.source]
type = "git"
url = "https://github.com/python-poetry/poetry.git"
reference = "develop"
resolved_reference = "123456"

[[package]]
name = "url-package"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.source]
type = "url"
url = "https://example.org/url-package-1.0-cp39-manylinux_2_17_x86_64.whl"

[[package]]
name = "url-package"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.source]
type = "url"
url = "https://example.org/url-package-1.0-cp39-win_amd64.whl"

[metadata]
lock-version = "1.1"
python-versions = "*"
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"

[metadata.files]
A = [
    {file = "bar", hash = "123"},
    {file = "foo", hash = "456"},
    {file = "baz", hash = "345"},
]
B = []
git-package = []
url-package = []
"""

    assert content == expected
Esempio n. 22
0
    def create_poetry(
        self,
        cwd: Path | None = None,
        io: IO | None = None,
        disable_plugins: bool = False,
        disable_cache: bool = False,
    ) -> Poetry:
        if io is None:
            io = NullIO()

        base_poetry = super().create_poetry(cwd)

        locker = Locker(base_poetry.file.parent / "poetry.lock",
                        base_poetry.local_config)

        # Loading global configuration
        with warnings.catch_warnings():
            # this is preserved to ensure export plugin tests pass in ci,
            # once poetry-plugin-export version is updated to use one that do not
            # use Factory.create_config(), this can be safely removed.
            warnings.filterwarnings("ignore", category=DeprecationWarning)
            config = self.create_config()

        # Loading local configuration
        local_config_file = TOMLFile(base_poetry.file.parent / "poetry.toml")
        if local_config_file.exists():
            if io.is_debug():
                io.write_line(
                    f"Loading configuration file {local_config_file.path}")

            config.merge(local_config_file.read())

        # Load local sources
        repositories = {}
        existing_repositories = config.get("repositories", {})
        for source in base_poetry.pyproject.poetry_config.get("source", []):
            name = source.get("name")
            url = source.get("url")
            if name and url and name not in existing_repositories:
                repositories[name] = {"url": url}

        config.merge({"repositories": repositories})

        poetry = Poetry(
            base_poetry.file.path,
            base_poetry.local_config,
            base_poetry.package,
            locker,
            config,
        )

        # Configuring sources
        self.configure_sources(
            poetry,
            poetry.local_config.get("source", []),
            config,
            io,
            disable_cache=disable_cache,
        )

        plugin_manager = PluginManager(Plugin.group,
                                       disable_plugins=disable_plugins)
        plugin_manager.load_plugins()
        poetry.set_plugin_manager(plugin_manager)
        plugin_manager.activate(poetry, io)

        return poetry
Esempio n. 23
0
def test_locker_properly_loads_nested_extras(locker: Locker):
    content = """\
[[package]]
name = "a"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
b = {version = "^1.0", optional = true, extras = "c"}

[package.extras]
b = ["b[c] (>=1.0,<2.0)"]

[[package]]
name = "b"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[package.dependencies]
c = {version = "^1.0", optional = true}

[package.extras]
c = ["c (>=1.0,<2.0)"]

[[package]]
name = "c"
version = "1.0"
description = ""
category = "main"
optional = false
python-versions = "*"

[metadata]
python-versions = "*"
lock-version = "1.1"
content-hash = "123456789"

[metadata.files]
"a" = []
"b" = []
"c" = []
"""

    locker.lock.write(tomlkit.parse(content))

    repository = locker.locked_repository()
    assert len(repository.packages) == 3

    packages = repository.find_packages(get_dependency("a", "1.0"))
    assert len(packages) == 1

    package = packages[0]
    assert len(package.requires) == 1
    assert len(package.extras) == 1

    dependency_b = package.extras["b"][0]
    assert dependency_b.name == "b"
    assert dependency_b.extras == frozenset({"c"})

    packages = repository.find_packages(dependency_b)
    assert len(packages) == 1

    package = packages[0]
    assert len(package.requires) == 1
    assert len(package.extras) == 1

    dependency_c = package.extras["c"][0]
    assert dependency_c.name == "c"
    assert dependency_c.extras == frozenset()

    packages = repository.find_packages(dependency_c)
    assert len(packages) == 1