diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..069a55bb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + branches: + - master + - cli-v2 + pull_request: + branches: + - master + - cli-v2 + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Unit tests (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: "${{ matrix.python-version }}" + allow-prereleases: true + - name: Install dependencies + run: | + pip install --upgrade pip + pip install ".[dev]" + # Python 3.9 needs tomli for pyproject.toml parsing in tests + if python -c "import sys; sys.exit(0 if sys.version_info < (3, 11) else 1)"; then + pip install "tomli>=1.0" + fi + - name: Run unit tests and microbenchmarks (correctness only) + run: pytest tests/unit/ tests/microbenchmarks/ --benchmark-disable -v --reruns 2 --reruns-delay 5 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 8d1567dc..c99f3bc7 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -5,21 +5,35 @@ on: tags: - "[0-9]+.[0-9]+.[0-9]+" +permissions: + contents: read + jobs: test: - name: Run unit tests + name: Unit tests (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 with: - python-version: "3.11" + python-version: "${{ matrix.python-version }}" + allow-prereleases: true - name: Install dependencies - run: pip install ".[dev]" + run: | + pip install --upgrade pip + pip install ".[dev]" + # Python 3.9 needs tomli for pyproject.toml parsing in tests + if python -c "import sys; sys.exit(0 if sys.version_info < (3, 11) else 1)"; then + pip install "tomli>=1.0" + fi - name: Run unit tests and microbenchmarks (correctness only) run: pytest tests/unit/ tests/microbenchmarks/ --benchmark-disable -v --reruns 2 --reruns-delay 5 @@ -28,12 +42,12 @@ jobs: needs: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" - name: Install build @@ -41,7 +55,7 @@ jobs: - name: Build sdist and wheel run: python -m build - name: Upload distributions - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: python-package-distributions path: dist/ @@ -59,7 +73,7 @@ jobs: contents: read steps: - name: Download distributions - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: python-package-distributions path: dist/ diff --git a/limacharlie/cli.py b/limacharlie/cli.py index edd4827d..2f3660de 100644 --- a/limacharlie/cli.py +++ b/limacharlie/cli.py @@ -105,6 +105,7 @@ def _config_no_warnings() -> bool: "case": ("case_cmd", "group"), "cloud-adapter": ("cloud_sensor", "group"), "completion": ("completion", "cmd"), + "config": ("config_cmd", "group"), "detection": ("detection", "group"), "download": ("download", "group"), "dr": ("dr", "group"), diff --git a/tests/microbenchmarks/test_cli_startup_microbenchmark.py b/tests/microbenchmarks/test_cli_startup_microbenchmark.py index 309c68b7..101bd08f 100644 --- a/tests/microbenchmarks/test_cli_startup_microbenchmark.py +++ b/tests/microbenchmarks/test_cli_startup_microbenchmark.py @@ -108,6 +108,11 @@ def test_cli_import_does_not_load_output(self): to avoid ~14ms of jmespath/tabulate/yaml/csv import overhead on fast paths like --help, --version, and --ai-help. """ + # Track which third-party deps are already loaded by other tests + # in the same process so we only assert on *newly* imported ones. + third_party_deps = ("jmespath", "tabulate", "yaml") + already_loaded = {dep for dep in third_party_deps if dep in sys.modules} + to_remove = [k for k in sys.modules if k.startswith("limacharlie")] saved = {k: sys.modules.pop(k) for k in to_remove} try: @@ -115,7 +120,9 @@ def test_cli_import_does_not_load_output(self): assert "limacharlie.output" not in sys.modules, ( "limacharlie.output imported at module level" ) - for dep in ("jmespath", "tabulate", "yaml"): + for dep in third_party_deps: + if dep in already_loaded: + continue assert dep not in sys.modules, ( f"{dep} imported at module level via limacharlie.output" ) diff --git a/tests/unit/test_cli_lazy_loading_regression.py b/tests/unit/test_cli_lazy_loading_regression.py index dc3dc25f..e3fac1da 100644 --- a/tests/unit/test_cli_lazy_loading_regression.py +++ b/tests/unit/test_cli_lazy_loading_regression.py @@ -46,7 +46,7 @@ # Every top-level command/group that must be registered on cli. EXPECTED_TOP_LEVEL_COMMANDS = frozenset({ "ai", "api", "api-key", "arl", "artifact", "audit", "auth", "billing", - "case", "cloud-adapter", "completion", "detection", "download", "dr", + "case", "cloud-adapter", "completion", "config", "detection", "download", "dr", "endpoint-policy", "event", "exfil", "extension", "external-adapter", "fp", "group", "help", "hive", "ingestion-key", "installation-key", "integrity", "ioc", "job", "logging", "lookup", "note", "org", "output", @@ -70,6 +70,7 @@ "case_cmd": ("group", "case"), "cloud_sensor": ("group", "cloud-adapter"), "completion": ("cmd", "completion"), + "config_cmd": ("group", "config"), "detection": ("group", "detection"), "download": ("group", "download"), "dr": ("group", "dr"), @@ -124,6 +125,7 @@ "get-token", "list-envs", "list-orgs", "login", "logout", "signup", "test", "use-env", "use-org", "whoami", }), + "config": frozenset({"migrate", "show-paths"}), "billing": frozenset({"details", "invoice", "plans", "status"}), "case": frozenset({ "add-note", "artifact", "assignees", "bulk-update",