From 58faa67cdaf367c602e9b34f02ee8ef8db330601 Mon Sep 17 00:00:00 2001 From: Ben Alkov Date: Mon, 11 Nov 2024 14:22:17 -0500 Subject: [PATCH] feat(yarn_classic): implement 'yarn.lock' support ## What - Ensure that 'yarn.lock' file exists - Read from 'yarn.lock' ## How - add 'pyarn' to dependencies (to parse 'yarn.lock' file) - implement `YarnLock` CommonConfigFile subclass Signed-off-by: Ben Alkov --- .../package_managers/yarn_classic/project.py | 45 ++++++- pyproject.toml | 1 + requirements-extras.txt | 17 ++- requirements.txt | 110 ++++++++++++++++++ .../yarn_classic/test_project.py | 33 +++++- 5 files changed, 201 insertions(+), 5 deletions(-) diff --git a/cachi2/core/package_managers/yarn_classic/project.py b/cachi2/core/package_managers/yarn_classic/project.py index 25d867e83..a1a58e57f 100644 --- a/cachi2/core/package_managers/yarn_classic/project.py +++ b/cachi2/core/package_managers/yarn_classic/project.py @@ -11,6 +11,8 @@ from dataclasses import dataclass from typing import Any, Literal, Union +from pyarn import lockfile # type: ignore + from cachi2.core.errors import PackageRejected from cachi2.core.rooted_path import RootedPath @@ -91,7 +93,48 @@ def from_file(cls, path: RootedPath) -> "PackageJson": return cls(path, package_json_data) -ConfigFile = Union[PackageJson] +class YarnLock(_CommonConfigFile): + """A yarn.lock file. + + This class abstracts the underlying attributes. + """ + + yarn_lockfile: lockfile.Lockfile + + @property + def config_kind(self) -> ConfigKind: + """Return kind of this ConfigFile.""" + return "yarnlock" + + @classmethod + def from_file(cls, path: RootedPath) -> "YarnLock": + """Parse the content of a yarn.lock file.""" + try: + yarn_lockfile = lockfile.Lockfile.from_file(path) + except FileNotFoundError: + raise PackageRejected( + reason="The yarn.lock file must be present for the yarn package manager", + solution=( + "Please double-check that you have specified the correct path " + "to the package directory containing this file" + ), + ) + except ValueError: + raise PackageRejected( + reason=f"Can't parse the {path} file.\n", + solution="The yarn.lock file must be valid.", + ) + + if not yarn_lockfile: + raise PackageRejected( + reason="The yarn.lock file must not be empty", + solution="Please verify the content of the file.", + ) + + return cls(path, yarn_lockfile.data) + + +ConfigFile = Union[PackageJson, YarnLock] @dataclass(frozen=True) diff --git a/pyproject.toml b/pyproject.toml index 383e07c18..5593db0fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "packaging", "pydantic", "pypi-simple", + "pyarn", "pyyaml", "requests", "semver", diff --git a/requirements-extras.txt b/requirements-extras.txt index 13b921ba6..2ab94fd9e 100644 --- a/requirements-extras.txt +++ b/requirements-extras.txt @@ -351,9 +351,6 @@ coverage[toml]==7.6.4 \ --hash=sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c \ --hash=sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858 # via pytest-cov -exceptiongroup==1.2.2 \ - --hash=sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b \ - --hash=sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc createrepo-c==1.1.4 \ --hash=sha256:0bdca28b4ce0b300e0caa2119e9eccb0fe22502d65bd4499efaec9415a1438bf \ --hash=sha256:5826daf92f994d48351c0d9dcf1fb6b09499687babb9b16dfcf17984af619d29 \ @@ -364,6 +361,10 @@ createrepo-c==1.1.4 \ --hash=sha256:9930b8ddac9cc808de06453cc7d4712940898140e415d23fe90a76cd53899b33 \ --hash=sha256:d8130f05be23feaa2028871c44474706203ca716a55df582d7e0e860430a24ea \ --hash=sha256:f547f7ea4748cee93e8b7033fe27e1a69ed922606a7fbd162acdcbdbe3af0715 + # via cachi2 (pyproject.toml) +exceptiongroup==1.2.2 \ + --hash=sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b \ + --hash=sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc # via pytest flake8==7.1.1 \ --hash=sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38 \ @@ -681,6 +682,10 @@ pluggy==1.5.0 \ --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 # via pytest +ply==3.11 \ + --hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \ + --hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce + # via pyarn propcache==0.2.0 \ --hash=sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9 \ --hash=sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763 \ @@ -781,6 +786,10 @@ propcache==0.2.0 \ --hash=sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016 \ --hash=sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504 # via yarl +pyarn==0.2.0 \ + --hash=sha256:542ff739af2b81a1200776eff2b4d2566a330846decbd0f815999b196d7b067d \ + --hash=sha256:d06e8b79bb830f142187b57ee664dc0104f658efdb2b2bae7ed99eaf7746eb1a + # via cachi2 (pyproject.toml) pycodestyle==2.12.1 \ --hash=sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3 \ --hash=sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521 @@ -1144,9 +1153,11 @@ typing-extensions==4.12.2 \ # via # black # cachi2 (pyproject.toml) + # multidict # mypy # pydantic # pydantic-core + # rich # typer urllib3==2.2.3 \ --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ diff --git a/requirements.txt b/requirements.txt index bb0a47803..167b3b573 100644 --- a/requirements.txt +++ b/requirements.txt @@ -489,6 +489,114 @@ packaging==24.1 \ # via # cachi2 (pyproject.toml) # pypi-simple +ply==3.11 \ + --hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \ + --hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce + # via pyarn +propcache==0.2.0 \ + --hash=sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9 \ + --hash=sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763 \ + --hash=sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325 \ + --hash=sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb \ + --hash=sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b \ + --hash=sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09 \ + --hash=sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957 \ + --hash=sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68 \ + --hash=sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f \ + --hash=sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798 \ + --hash=sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418 \ + --hash=sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6 \ + --hash=sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162 \ + --hash=sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f \ + --hash=sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036 \ + --hash=sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8 \ + --hash=sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2 \ + --hash=sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110 \ + --hash=sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23 \ + --hash=sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8 \ + --hash=sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638 \ + --hash=sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a \ + --hash=sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44 \ + --hash=sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2 \ + --hash=sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2 \ + --hash=sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850 \ + --hash=sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136 \ + --hash=sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b \ + --hash=sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887 \ + --hash=sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89 \ + --hash=sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87 \ + --hash=sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348 \ + --hash=sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4 \ + --hash=sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861 \ + --hash=sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e \ + --hash=sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c \ + --hash=sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b \ + --hash=sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb \ + --hash=sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1 \ + --hash=sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de \ + --hash=sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354 \ + --hash=sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563 \ + --hash=sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5 \ + --hash=sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf \ + --hash=sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9 \ + --hash=sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12 \ + --hash=sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4 \ + --hash=sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5 \ + --hash=sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71 \ + --hash=sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9 \ + --hash=sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed \ + --hash=sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336 \ + --hash=sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90 \ + --hash=sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063 \ + --hash=sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad \ + --hash=sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6 \ + --hash=sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8 \ + --hash=sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e \ + --hash=sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2 \ + --hash=sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7 \ + --hash=sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d \ + --hash=sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d \ + --hash=sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df \ + --hash=sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b \ + --hash=sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178 \ + --hash=sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2 \ + --hash=sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630 \ + --hash=sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48 \ + --hash=sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61 \ + --hash=sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89 \ + --hash=sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb \ + --hash=sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3 \ + --hash=sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6 \ + --hash=sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562 \ + --hash=sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b \ + --hash=sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58 \ + --hash=sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db \ + --hash=sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99 \ + --hash=sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37 \ + --hash=sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83 \ + --hash=sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a \ + --hash=sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d \ + --hash=sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04 \ + --hash=sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70 \ + --hash=sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544 \ + --hash=sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394 \ + --hash=sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea \ + --hash=sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7 \ + --hash=sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1 \ + --hash=sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793 \ + --hash=sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577 \ + --hash=sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7 \ + --hash=sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57 \ + --hash=sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d \ + --hash=sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032 \ + --hash=sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d \ + --hash=sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016 \ + --hash=sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504 + # via yarl +pyarn==0.2.0 \ + --hash=sha256:542ff739af2b81a1200776eff2b4d2566a330846decbd0f815999b196d7b067d \ + --hash=sha256:d06e8b79bb830f142187b57ee664dc0104f658efdb2b2bae7ed99eaf7746eb1a + # via cachi2 (pyproject.toml) pydantic==2.9.1 \ --hash=sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2 \ --hash=sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612 @@ -687,8 +795,10 @@ typing-extensions==4.12.2 \ --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 # via + # multidict # pydantic # pydantic-core + # rich # typer urllib3==2.2.3 \ --hash=sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac \ diff --git a/tests/unit/package_managers/yarn_classic/test_project.py b/tests/unit/package_managers/yarn_classic/test_project.py index 974c3093c..b98fceed9 100644 --- a/tests/unit/package_managers/yarn_classic/test_project.py +++ b/tests/unit/package_managers/yarn_classic/test_project.py @@ -1,10 +1,16 @@ import json import pytest +from pyarn import lockfile # type: ignore from cachi2.core.errors import PackageRejected from cachi2.core.package_managers.yarn_classic.main import _verify_repository -from cachi2.core.package_managers.yarn_classic.project import ConfigFile, PackageJson, Project +from cachi2.core.package_managers.yarn_classic.project import ( + ConfigFile, + PackageJson, + Project, + YarnLock, +) from cachi2.core.rooted_path import RootedPath VALID_PACKAGE_JSON_FILE = """ @@ -26,6 +32,24 @@ INVALID_JSON_FILE = "totally not json" +VALID_YARN_LOCK_FILE = """ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 +package-1@^1.0.0: + version "1.0.3" + resolved "https://registry.npmjs.org/package-1/-/package-1-1.0.3.tgz#a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0" +""" + +EMPTY_YARN_LOCK_FILE = """ + +""" + +INVALID_YARN_LOCK_FILE = """ +# yarn lockfile v1 +package-1 + version "1.0.3" +""" + def _prepare_config_file( rooted_tmp_path: RootedPath, config_file_class: ConfigFile, filename: str, content: str @@ -51,6 +75,7 @@ def _setup_pnp_installs(rooted_tmp_path: RootedPath, pnp_kind: str) -> None: pytest.param( PackageJson, "package.json", VALID_PACKAGE_JSON_FILE, "package_json", id="package_json" ), + pytest.param(YarnLock, "yarn.lock", VALID_YARN_LOCK_FILE, "yarnlock", id="yarnlock"), ], ) def test_config_file_attributes( @@ -76,6 +101,7 @@ def test_config_file_attributes( pytest.param( PackageJson, "package.json", VALID_PACKAGE_JSON_FILE, "json", id="package_json" ), + pytest.param(YarnLock, "yarn.lock", VALID_YARN_LOCK_FILE, "pyarn", id="yarnlock"), ], ) def test_find_and_open_config_file( @@ -94,6 +120,8 @@ def test_find_and_open_config_file( if content_kind == "json": assert found_config.data == json.loads(config_file_content) + elif content_kind == "pyarn": + assert found_config.data == lockfile.Lockfile.from_str(config_file_content).data @pytest.mark.parametrize( @@ -105,6 +133,8 @@ def test_find_and_open_config_file( INVALID_JSON_FILE, id="invalid_package_json", ), + pytest.param(YarnLock, "yarn.lock", EMPTY_YARN_LOCK_FILE, id="empty_yarnlock"), + pytest.param(YarnLock, "yarn.lock", INVALID_YARN_LOCK_FILE, id="invalid_yarnlock"), ], ) def test_from_file_bad( @@ -130,6 +160,7 @@ def test_from_file_bad( "package.json", id="missing_package_json", ), + pytest.param(YarnLock, "yarn.lock", id="missing_yarnlock"), ], ) def test_from_file_missing(