diff options
| -rw-r--r-- | .github/workflows/test.yml | 26 | ||||
| -rw-r--r-- | pdm.lock | 49 | ||||
| -rw-r--r-- | pyproject.toml | 3 | ||||
| -rw-r--r-- | src/ipm/__main__.py | 97 | ||||
| -rw-r--r-- | src/ipm/api.py | 79 | ||||
| -rw-r--r-- | src/ipm/const.py | 6 | ||||
| -rw-r--r-- | src/ipm/exceptions.py | 12 | ||||
| -rw-r--r-- | src/ipm/logging.py | 12 | ||||
| -rw-r--r-- | src/ipm/models/ipk.py | 11 | ||||
| -rw-r--r-- | src/ipm/utils/freeze.py | 18 | ||||
| -rw-r--r-- | test/infini.toml | 9 | ||||
| -rw-r--r-- | test/src/__init__.py | 1 | ||||
| -rw-r--r-- | tests/test-api.py | 64 | ||||
| -rw-r--r-- | tests/test_api.py | 20 | ||||
| -rw-r--r-- | tests/test_cli.py | 64 |
15 files changed, 323 insertions, 148 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0a6958f..f0c1b6c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,8 @@ on: workflow_dispatch: jobs: - test-api: - name: test commit + tests: + name: Test IPM runs-on: ubuntu-latest permissions: id-token: write @@ -17,18 +17,14 @@ jobs: with: python-version: "3.9" - - name: setup pdm - run: pip install pdm - - - run: pdm install - - - name: install test deps + - name: Setup PDM run: | - pdm install -dG test - # pip install pytest - - - name: test api + pip install pdm + pdm add pytest + + - name: Install IPM + run: pdm install + + - name: Test API run: | - cd tests - pdm venv activate in-project - pdm run test-api.py
\ No newline at end of file + pdm run python -m pytest tests/ @@ -5,7 +5,11 @@ groups = ["default", "test"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" +<<<<<<< HEAD +content_hash = "sha256:9470f87391867615509911906fda898fd0fc55d1edecca94597578d34f5713ab" +======= content_hash = "sha256:7e92b84c2b52abef8bf2cca785657bbd0686221a6d3174500294332114b6df90" +>>>>>>> 55623b0cb2e0cb49f5ff4f456121b07c2376659c [[package]] name = "certifi" @@ -75,12 +79,31 @@ files = [ ] [[package]] +name = "click" +version = "8.1.7" +requires_python = ">=3.7" +summary = "Composable command line interface toolkit" +groups = ["default"] +dependencies = [ + "colorama; platform_system == \"Windows\"", +] +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[[package]] name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." +<<<<<<< HEAD +groups = ["default"] +marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" +======= groups = ["default", "test"] marker = "sys_platform == \"win32\"" +>>>>>>> 55623b0cb2e0cb49f5ff4f456121b07c2376659c files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -231,6 +254,32 @@ files = [ ] [[package]] +name = "typer" +version = "0.9.0" +requires_python = ">=3.6" +summary = "Typer, build great CLIs. Easy to code. Based on Python type hints." +groups = ["default"] +dependencies = [ + "click<9.0.0,>=7.1.1", + "typing-extensions>=3.7.4.3", +] +files = [ + {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, + {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, +] + +[[package]] +name = "typing-extensions" +version = "4.9.0" +requires_python = ">=3.8" +summary = "Backported and Experimental Type Hints for Python 3.8+" +groups = ["default"] +files = [ + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, +] + +[[package]] name = "urllib3" version = "2.1.0" requires_python = ">=3.8" diff --git a/pyproject.toml b/pyproject.toml index fc41f6c..d10bb74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ipm" -version = "0.1.0-alpha.2" +version = "0.1.0-alpha.3" description = "Infini Package Manager" authors = [ { name = "苏向夜", email = "fu050409@163.com" }, @@ -11,6 +11,7 @@ dependencies = [ "pytest>=7.4.4", "toml>=0.10.2", "requests>=2.31.0", + "typer>=0.9.0", ] requires-python = ">=3.10" readme = "README.md" diff --git a/src/ipm/__main__.py b/src/ipm/__main__.py index c40e86e..753c3a7 100644 --- a/src/ipm/__main__.py +++ b/src/ipm/__main__.py @@ -1,41 +1,62 @@ -from .api import install, extract, build -import argparse -import sys - -def main(): - parser = argparse.ArgumentParser( - prog="ipm", description="Infini 包管理器", exit_on_error=False - ) - subparsers = parser.add_subparsers( - title="指令", dest="command", metavar="<operation>" - ) - - # Install command - install_parser = subparsers.add_parser("install", help="安装一个 Infini 规则包到此计算机") - install_parser.add_argument("uri", help="Infini 包的统一资源标识符") - install_parser.add_argument("--index", help="IPM 包服务器") - - # Extract command - extract_parser = subparsers.add_parser("extract", help="解压缩 Infini 包") - extract_parser.add_argument("package", help="Infini 包路径") - extract_parser.add_argument( - "--dist", - default=".", - help="特定的解压路径 (默认: 当前工作目录)", - ) - - # Build command - build_parser = subparsers.add_parser("build", help="打包 Infini 规则包") - build_parser.add_argument("package", nargs="?", help="Infini 库路径", default=".") - - args = parser.parse_args(sys.argv[1:] or ["-h"]) - - if args.command == "install": - install(args.uri, args.index, echo=True) - elif args.command == "extract": - extract(args.package, args.dist) - elif args.command == "build": - build(args.package) +from . import api +from .exceptions import IpmException +from .logging import logger +import typer + +main = typer.Typer( + name="ipm", help="Infini 包管理器", no_args_is_help=True, add_completion=False +) + + +@main.command() +def install( + uri: str = typer.Argument(help="Infini 包的统一资源标识符"), + index: str = typer.Option(None, help="IPM 包服务器"), +): + """安装一个 Infini 规则包到此计算机""" + try: + api.install(uri, index, echo=True) + except IpmException as error: + logger.error(error) + + +@main.command() +def extract( + package: str = typer.Argument(help="Infini 项目路径"), + dist: str = typer.Option(".", help="特定的解压路径"), +): + """解压缩 Infini 包""" + try: + api.extract(package, dist, echo=True) + except IpmException as error: + logger.error(error) + + +@main.command() +def init(force: bool = typer.Option(None, "--force", "-f", help="强制初始化")): + """初始化一个 Infini 项目""" + try: + api.init(".", force, echo=True) + except IpmException as error: + logger.error(error) + + +@main.command() +def new(package: str = typer.Argument(help="Infini 项目路径")): + """新建一个 Infini 项目""" + try: + api.new(package, echo=True) + except IpmException as error: + logger.error(error) + + +@main.command() +def build(package: str = typer.Argument(".", help="Infini 项目路径")): + """打包 Infini 规则包""" + try: + api.build(package, echo=True) + except IpmException as error: + logger.error(error) if __name__ == "__main__": diff --git a/src/ipm/api.py b/src/ipm/api.py index faf7e0b..2cb6eee 100644 --- a/src/ipm/api.py +++ b/src/ipm/api.py @@ -1,16 +1,58 @@ from pathlib import Path from .typing import StrPath from .utils import freeze, urlparser, loader -from .models.ipk import InfiniPackage -from .exceptions import FileTypeMismatch +from .models.ipk import InfiniPackage, InfiniFrozenPackage +from .exceptions import FileTypeMismatch, TomlLoadFailed, FileNotFoundError from .const import INDEX, HOME -from .logging import info, success +from .logging import info, success, warning, error -import os +import toml -def build(source_path: StrPath, echo: bool = False) -> None: - freeze.build_ipk(InfiniPackage(source_path)) +def init(source_path: StrPath, force: bool = False, echo: bool = False) -> None: + source_path = Path(source_path).resolve() + if (toml_path := (source_path / "infini.toml")).exists() and not force: + warning(f"无法在已经初始化的地址重新初始化, 如果你的确希望重新初始化, 请使用[ipm init --force].", echo) + + toml_file = toml_path.open("w", encoding="utf-8") + toml.dump( + { + "infini": { + "name": source_path.name, + "version": "0.1.0", + "description": "COC 规则包", + "license": "MIT", + }, + "requirements": {}, + "dependencies": {}, + }, + toml_file, + ) + toml_file.close() + + (source_path / "src").mkdir(parents=True, exist_ok=True) + (source_path / "src" / "__init__.py").write_text( + "# Initialized `__init__.py` generated by ipm." + ) + + +def new(dist_path: StrPath, echo: bool = False) -> None: + info("初始化环境中...") + path = Path(dist_path).resolve() + if path.exists(): + return warning(f"路径[{path}]已经存在.", echo) + path.mkdir(parents=True, exist_ok=True) + return init(path, echo=echo) + + +def build(source_path: StrPath, echo: bool = False) -> InfiniFrozenPackage: + info("检查构建环境...", echo) + try: + ipk = InfiniPackage(source_path) + info(f"包[{ipk.name}]构建环境载入完毕.", echo) + except TomlLoadFailed as e: + return error(f"环境存在异常: {e}", echo) + return freeze.build_ipk(ipk, echo) def extract( @@ -28,12 +70,8 @@ def install(uri: str, index: str = "", echo: bool = False) -> None: HOME.mkdir(parents=True, exist_ok=True) index = index or INDEX - if os.path.isabs(uri): - info(f"检定给定的 URI 地址[{uri}]为本地路径.", echo) - if not uri.endswith(".ipk"): - raise FileTypeMismatch("文件类型与预期[.ipk]不匹配.") - info("安装中...", echo) - ipk = extract(Path(uri).resolve(), HOME, echo) + if uri.isalpha(): + ... elif urlparser.is_valid_url(uri): filename = uri.rstrip("/").split("/")[-1] ipk = loader.load( @@ -41,9 +79,20 @@ def install(uri: str, index: str = "", echo: bool = False) -> None: uri.rstrip("/").rsplit("/")[0], filename, ) - elif uri.isalpha(): - ... else: - raise FileTypeMismatch("URI指向未知的位置.") + info(f"检定给定的 URI 地址[{uri}]为本地路径.", echo) + path = Path(uri).resolve() + if not path.exists(): + raise FileNotFoundError("给定的 URI 路径不存在!") + + if uri.endswith(".ipk"): + info("安装中...", echo) + ipk = extract(Path(uri).resolve(), HOME, echo) + else: + raise FileTypeMismatch("文件类型与预期[.ipk]不匹配.") success(f"包[{ipk.name}]成功安装在[{ipk.source_path}].", echo) + + +def uninstall(ipk: str | InfiniPackage): + ... diff --git a/src/ipm/const.py b/src/ipm/const.py index c682fdd..d31b10a 100644 --- a/src/ipm/const.py +++ b/src/ipm/const.py @@ -1,8 +1,12 @@ from pathlib import Path +# 控制参数 DEBUG = False +# 初始化参数 INDEX = "https://ipm.hydroroll.team/index/" HOME = Path.home() / ".ipm" / "src" + +# 文本参数 ATTENSION = """# This file is @generated by IPM. -# It is not intended for manual editing.""" +# It is not intended for manual editing.\n\n""" diff --git a/src/ipm/exceptions.py b/src/ipm/exceptions.py index e6a01c0..b19b60c 100644 --- a/src/ipm/exceptions.py +++ b/src/ipm/exceptions.py @@ -2,11 +2,19 @@ class IpmException(Exception): """IPM Base Exception""" -class FileNotFoundError(IpmException, FileNotFoundError): +class FileException(IpmException): + """IPM File Base Exception""" + + +class TomlLoadFailed(FileException): + """Failed to load `infini.toml`""" + + +class FileNotFoundError(FileException, FileNotFoundError): """Raises when file not founded""" -class FileExistsError(IpmException, FileExistsError): +class FileExistsError(FileException, FileExistsError): """Raises when file not founded""" diff --git a/src/ipm/logging.py b/src/ipm/logging.py index 2594930..f923e64 100644 --- a/src/ipm/logging.py +++ b/src/ipm/logging.py @@ -4,9 +4,17 @@ from .const import DEBUG logger = multilogger(name="IPM", level="DEBUG" if DEBUG else "INFO", notime=True) -def info(message: str, echo: bool = True) -> None: +def info(message: str, echo: bool = False) -> None: return logger.info(message) if echo else None -def success(message: str, echo: bool = True) -> None: +def success(message: str, echo: bool = False) -> None: return logger.success(message) if echo else None + + +def warning(message: str, echo: bool = False) -> None: + return logger.warning(message) if echo else None + + +def error(message: str, echo: bool = False) -> None: + return logger.error(message) if echo else None diff --git a/src/ipm/models/ipk.py b/src/ipm/models/ipk.py index a50ebde..27a3d68 100644 --- a/src/ipm/models/ipk.py +++ b/src/ipm/models/ipk.py @@ -1,6 +1,6 @@ from pathlib import Path from ..typing import List, Dict, Literal -from ..exceptions import SyntaxError +from ..exceptions import SyntaxError, TomlLoadFailed import toml @@ -39,7 +39,11 @@ class InfiniPackage: self.source_path = Path(path).resolve() toml_path = self.source_path / "infini.toml" - data_load = toml.load(toml_path.open("r", encoding="utf-8")) + try: + data_load = toml.load(toml_path.open("r", encoding="utf-8")) + except Exception as error: + raise TomlLoadFailed(f"项目文件[infini.toml]导入失败: {error}") from error + if "infini" not in data_load.keys(): raise SyntaxError("配置文件中缺少[infini]项.") @@ -58,9 +62,6 @@ class InfiniPackage: def hash_name(self) -> str: return f"{self.name}-{self.version}.ipk.hash" - # @property - # def home_p - class InfiniFrozenPackage: source_path: Path diff --git a/src/ipm/utils/freeze.py b/src/ipm/utils/freeze.py index 74f4b40..5c6951c 100644 --- a/src/ipm/utils/freeze.py +++ b/src/ipm/utils/freeze.py @@ -10,8 +10,9 @@ import tempfile import shutil -def build_ipk(ipk: InfiniPackage) -> InfiniFrozenPackage: - build_dir = ipk.source_path / ".build" +def build_ipk(ipk: InfiniPackage, echo: bool = False) -> InfiniFrozenPackage: + info("正在初始化开发环境...", echo) + build_dir = ipk.source_path / "build" src_path = ipk.source_path / "src" dist_path = ipk.source_path / "dist" ifp_path = dist_path / ipk.default_name @@ -24,28 +25,35 @@ def build_ipk(ipk: InfiniPackage) -> InfiniFrozenPackage: dist_path.mkdir(parents=True, exist_ok=True) build_dir.mkdir(parents=True, exist_ok=True) + info("开发环境构建完成, 开始复制工程文件...", echo) shutil.copytree(src_path, build_dir / "src") shutil.copy2(ipk.source_path / "infini.toml", build_dir / "infini.toml") + info("工程文件复制完毕, 开始打包[ipk]文件...", echo) _freeze.create_tar_gz( str(build_dir), str(ifp_path), ) - (dist_path / ipk.hash_name).write_bytes(ifp_hash(ifp_path)) + success(f"打包文件已存至[{ifp_path}].", echo) + info("开始创建SHA256验证文件...", echo) + hash_bytes = ifp_hash(ifp_path) + info(f"文件SHA256值为[{hash_bytes.hex()}].", echo) + (dist_path / ipk.hash_name).write_bytes(hash_bytes) + success(f"包[{ipk.name}]构建成功.", echo) return InfiniFrozenPackage(source_path=ifp_path, **{"name": ipk.name}) def extract_ipk( - source_path: StrPath, dist_path: str | Path, echo: bool = False + source_path: StrPath, dist_path: StrPath, echo: bool = False ) -> InfiniPackage: ifp_path = Path(source_path).resolve() dist_path = Path(dist_path).resolve() hash_path = ifp_path.parent / (ifp_path.name + ".hash") if not hash_path.exists(): - raise VerifyFailed("哈希文件不存在!") + raise VerifyFailed(f"哈希文件[{hash_path}]不存在!") if not ifp_verify(ifp_path, hash_path.read_bytes()): raise VerifyFailed("文件完整性验证失败!") diff --git a/test/infini.toml b/test/infini.toml new file mode 100644 index 0000000..cc0f720 --- /dev/null +++ b/test/infini.toml @@ -0,0 +1,9 @@ +[infini] +name = "test" +version = "0.1.0" +description = "COC 规则包" +license = "MIT" + +[requirements] + +[dependencies] diff --git a/test/src/__init__.py b/test/src/__init__.py new file mode 100644 index 0000000..d93492e --- /dev/null +++ b/test/src/__init__.py @@ -0,0 +1 @@ +# Initialized `__init__.py` generated by ipm.
\ No newline at end of file diff --git a/tests/test-api.py b/tests/test-api.py deleted file mode 100644 index 9afe40b..0000000 --- a/tests/test-api.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest -from ipm.__main__ import main -from unittest.mock import patch, MagicMock - -# Test IDs for parametrization -HAPPY_PATH_INSTALL = "happy_install" -HAPPY_PATH_EXTRACT = "happy_extract" -HAPPY_PATH_BUILD = "happy_build" -EDGE_CASE_NO_ARGS = "edge_no_args" -ERROR_CASE_UNKNOWN_COMMAND = "error_unknown_command" - -# Mock the sys.argv to simulate command line arguments -@pytest.fixture -def mock_sys_argv(monkeypatch): - def _mock_sys_argv(args): - monkeypatch.setattr("sys.argv", ["ipm"] + args) - return _mock_sys_argv - -# Mock the api functions to prevent actual execution -@pytest.fixture -def mock_api_functions(monkeypatch): - install_mock = MagicMock() - extract_mock = MagicMock() - build_mock = MagicMock() - monkeypatch.setattr("ipm.__main__.install", install_mock) - monkeypatch.setattr("ipm.__main__.extract", extract_mock) - monkeypatch.setattr("ipm.__main__.build", build_mock) - return install_mock, extract_mock, build_mock - -@pytest.mark.parametrize("test_id, args, expected_call", [ - # Happy path tests - (HAPPY_PATH_INSTALL, ["install", "http://ipm.hydroroll.team/package.ipk"], ("install", ["http://ipm.hydroroll.team/package.ipk", None])), - (HAPPY_PATH_EXTRACT, ["extract", "package.ipk", "--dist", "dist_folder"], ("extract", ["package.ipk", "dist_folder"])), - (HAPPY_PATH_BUILD, ["build", "source_folder"], ("build", ["source_folder"])), - - # Edge case tests - (EDGE_CASE_NO_ARGS, [], ("help", [])), - - # Error case tests - (ERROR_CASE_UNKNOWN_COMMAND, ["unknown", "arg"], ("error", ["unknown"])), -]) -def test_main_commands(test_id, args, expected_call, mock_sys_argv, mock_api_functions, capsys): - mock_sys_argv(args) - install_mock, extract_mock, build_mock = mock_api_functions - - # Act - with pytest.raises(SystemExit): # argparse exits the program when -h is called or on error - main() - - # Assert - if expected_call[0] == "install": - install_mock.assert_called_once_with(*expected_call[1], echo=True) - elif expected_call[0] == "extract": - extract_mock.assert_called_once_with(*expected_call[1]) - elif expected_call[0] == "build": - build_mock.assert_called_once_with(*expected_call[1]) - elif expected_call[0] == "help": - captured = capsys.readouterr() - assert "Infini 包管理器" in captured.out - elif expected_call[0] == "error": - captured = capsys.readouterr() - assert "error: unrecognized arguments" in captured.err - -pytest.main()
\ No newline at end of file diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..7136b36 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,20 @@ +from ipm import api + + +def test_new(): + api.new("test") + + +def test_build(): + api.new("test") + api.build("test") + + +def test_extract(): + api.build("test") + api.extract("test\\dist\\test-0.1.0.ipk") + + +def test_install(): + api.build("test") + api.install("test\\dist\\test-0.1.0.ipk") diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..8b22848 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,64 @@ +# import pytest +# from ipm.__main__ import main +# from unittest.mock import patch, MagicMock + +# # Test IDs for parametrization +# HAPPY_PATH_INSTALL = "happy_install" +# HAPPY_PATH_EXTRACT = "happy_extract" +# HAPPY_PATH_BUILD = "happy_build" +# EDGE_CASE_NO_ARGS = "edge_no_args" +# ERROR_CASE_UNKNOWN_COMMAND = "error_unknown_command" + +# # Mock the sys.argv to simulate command line arguments +# @pytest.fixture +# def mock_sys_argv(monkeypatch): +# def _mock_sys_argv(args): +# monkeypatch.setattr("sys.argv", ["ipm"] + args) +# return _mock_sys_argv + +# # Mock the api functions to prevent actual execution +# @pytest.fixture +# def mock_api_functions(monkeypatch): +# install_mock = MagicMock() +# extract_mock = MagicMock() +# build_mock = MagicMock() +# monkeypatch.setattr("ipm.__main__.install", install_mock) +# monkeypatch.setattr("ipm.__main__.extract", extract_mock) +# monkeypatch.setattr("ipm.__main__.build", build_mock) +# return install_mock, extract_mock, build_mock + +# @pytest.mark.parametrize("test_id, args, expected_call", [ +# # Happy path tests +# (HAPPY_PATH_INSTALL, ["install", "http://ipm.hydroroll.team/package.ipk"], ("install", ["http://ipm.hydroroll.team/package.ipk", None])), +# (HAPPY_PATH_EXTRACT, ["extract", "package.ipk", "--dist", "dist_folder"], ("extract", ["package.ipk", "dist_folder"])), +# (HAPPY_PATH_BUILD, ["build", "source_folder"], ("build", ["source_folder"])), + +# # Edge case tests +# (EDGE_CASE_NO_ARGS, [], ("help", [])), + +# # Error case tests +# (ERROR_CASE_UNKNOWN_COMMAND, ["unknown", "arg"], ("error", ["unknown"])), +# ]) +# def test_main_commands(test_id, args, expected_call, mock_sys_argv, mock_api_functions, capsys): +# mock_sys_argv(args) +# install_mock, extract_mock, build_mock = mock_api_functions + +# # Act +# with pytest.raises(SystemExit): # argparse exits the program when -h is called or on error +# main() + +# # Assert +# if expected_call[0] == "install": +# install_mock.assert_called_once_with(*expected_call[1], echo=True) +# elif expected_call[0] == "extract": +# extract_mock.assert_called_once_with(*expected_call[1]) +# elif expected_call[0] == "build": +# build_mock.assert_called_once_with(*expected_call[1]) +# elif expected_call[0] == "help": +# captured = capsys.readouterr() +# assert "Infini 包管理器" in captured.out +# elif expected_call[0] == "error": +# captured = capsys.readouterr() +# assert "error: unrecognized arguments" in captured.err + +# pytest.main()
\ No newline at end of file |
