From fcbf5a676019554dd3cfa0e98549e8eb0f272671 Mon Sep 17 00:00:00 2001 From: EmmonsCurse <1577972691@qq.com> Date: Wed, 20 May 2026 21:47:04 +0800 Subject: [PATCH 1/3] [CI] Skip CI for non-runtime directories and add unittest Claude skill --- .../write-fastdeploy-unittest/README.md | 54 ++ .../skills/write-fastdeploy-unittest/SKILL.md | 494 ++++++++++++++++++ .../skills/write-fastdeploy-unittest/debug.sh | 2 + .github/workflows/check-bypass.yml | 3 + 4 files changed, 553 insertions(+) create mode 100644 .claude/skills/write-fastdeploy-unittest/README.md create mode 100644 .claude/skills/write-fastdeploy-unittest/SKILL.md create mode 100644 .claude/skills/write-fastdeploy-unittest/debug.sh diff --git a/.claude/skills/write-fastdeploy-unittest/README.md b/.claude/skills/write-fastdeploy-unittest/README.md new file mode 100644 index 00000000000..9b5e363a869 --- /dev/null +++ b/.claude/skills/write-fastdeploy-unittest/README.md @@ -0,0 +1,54 @@ +# write-fastdeploy-unittest + +A skill that guides AI agents to write CI-compliant unit tests for the FastDeploy project. + +## Features + +- Automatically selects the appropriate test pattern based on the code under test (pure logic / GPU kernel / offline inference / E2E serving) +- Follows FastDeploy CI classification rules (multi-GPU sequential vs single-GPU parallel) +- Meets the 80% diff coverage PR threshold +- Correctly uses port variables, log isolation, and resource cleanup per CI conventions + +## Usage + +### Basic — specify a source file + +``` +Use the write-fastdeploy-unittest skill to add unit tests for fastdeploy/cache_manager/transfer_factory/file_store/file_store.py +``` + +### From coverage report — paste the line directly + +``` +Use the write-fastdeploy-unittest skill to add unit tests for: + +fastdeploy/model_executor/model_loader/default_loader.py 48 32 14 0 26% 37-38, 42, 46-52, 56-66, 69-97 +``` + +The coverage report format is: `file_path Stmts Miss Branch BrMiss Cover% Missing_lines`. The agent will focus on the uncovered lines and write tests specifically targeting those branches. + +The agent will automatically: +1. Read the target source file and analyze uncovered lines +2. Select the appropriate test pattern (Pattern 1-4) +3. Generate a test file in the corresponding `tests/` subdirectory +4. Run tests and verify coverage + +## Test Pattern Quick Reference + +| Pattern | Use Case | Dependencies | +|---------|----------|--------------| +| 1 — Pure Logic | config, utils, scheduler, router, etc. | No GPU; mock external deps | +| 2 — GPU Kernel | ops, layers, numerical computation | Requires GPU; `@pytest.mark.gpu` | +| 3 — Offline Inference | LLM API, model loading | Requires MODEL_PATH | +| 4 — E2E Serving | End-to-end HTTP serving | subprocess + ports | + +## Key Conventions + +- Test file naming: `test_.py` +- Test class naming: `Test` +- Coverage verification: `python -m coverage run --source= -m pytest && coverage report -m` +- The `--source` parameter must be a directory path, not a dotted module name + +## Related Files + +- [SKILL.md](SKILL.md) — Full skill instruction document diff --git a/.claude/skills/write-fastdeploy-unittest/SKILL.md b/.claude/skills/write-fastdeploy-unittest/SKILL.md new file mode 100644 index 00000000000..9cd39ccb604 --- /dev/null +++ b/.claude/skills/write-fastdeploy-unittest/SKILL.md @@ -0,0 +1,494 @@ +# Writing FastDeploy CI / Unit Tests +This skill covers **how to write and run tests** for FastDeploy. FastDeploy uses pytest for unit testing with automatic coverage collection. Tests are classified into **multi-GPU** (sequential) and **single-GPU** (parallel) categories for efficient CI execution. + +--- + +## Core Rules +1. **Use pytest, or unittest** — FastDeploy uses pytest as the test framework with fixtures for common patterns +2. **Follow test classification rules** — Tests are auto-classified by location and content (see Classification section below) +3. **Choose service startup approach as needed** — `FDRunner` (in `tests/conftest.py`) is a convenience wrapper for common test patterns, not a universal requirement; use `fastdeploy.entrypoints.llm.LLM` directly if it doesn't fit +4. **Isolate logs per test** — Use `FD_LOG_DIR` environment variable (auto-set by coverage_run.sh) to isolate test logs +5. **Clean up resources** — Use context manager or `try/finally` for service teardown +6. **Maintain coverage threshold** — PR changes require 80% diff coverage + +--- + +## Test Classification +FastDeploy's CI script (`scripts/coverage_run.sh`) classifies tests into two categories: + +### Multi-GPU Tests (Sequential) +Tests that run sequentially (cannot parallelize): + +|Rule|Pattern|Example| +|-|-|-| +|**Distributed tests**|`tests/distributed/test_*.py`|Multi-GPU communication tests| +|**E2E tests**|`tests/e2e/test_*.py`|Full serving integration tests| +|**Model loader tests**|`tests/model_loader/test_*.py`|Tests that allocate multiple GPUs| +|**Content patterns**|File contains any pattern below|Service tests, multi-GPU tests| + +Content patterns that trigger multi-GPU (sequential) classification — any match causes the file to run sequentially: + +``` +tensor_parallel_size.*=[1234] +--tensor-parallel-size.*[1234] +"tensor_parallel_size".*[1234] +CUDA_VISIBLE_DEVICES.*0.*1 +paddle.distributed.launch.*--gpus.*0.*1 +FD_API_PORT +FLASK_PORT +FD_ENGINE_QUEUE_PORT +FD_METRICS_PORT +FD_CACHE_QUEUE_PORT +FD_ROUTER_PORT +FD_CONNECTOR_PORT +FD_RDMA_PORT +``` + +> **Important**: Port variables (`FD_API_PORT`, etc.) are the primary trigger for multi-GPU classification — any test involving port usage (offline or online inference) runs sequentially. The `tensor_parallel_size` patterns cover `[1234]` which includes `=1`, so a single-GPU service test (TP=1) that also references a port variable is still classified as multi-GPU. + +### Single-GPU Tests (Parallel) +All other tests run in parallel. They are automatically split into 2 shards (one per GPU on the 2-card CI runner). + +--- + +## Test Directory Structure +``` +tests/ +├── batch_invariant/ # Batch processing invariance tests +├── cache_manager/ # KV cache management tests +├── ci_use/ # Used by a separate CI task (excluded from coverage runs) +├── ci_validation/ # CI validation tests (excluded from coverage runs) +│ ├── server/ # Server functionality tests +│ └── stable_cases/ # Stable/accuracy tests +├── conftest.py # Global pytest configuration and fixtures +├── cov_pytest.ini # Pytest config for coverage runs +├── deterministic/ # Determinism/reproducibility tests +├── distributed/ # Distributed communication tests (NCCL, RDMA) +├── e2e/ # End-to-end serving tests (ERNIE, Qwen, etc.) +├── engine/ # LLM engine tests +├── entrypoints/ # API entrypoint tests +├── input/ # Input processing/tokenization tests +├── layers/ # Layer/attention tests +├── logger/ # Logging tests +├── metrics/ # Prometheus/metrics tests +├── model_executor/ # Model executor tests +├── model_loader/ # Model loading/caching tests +├── multimodal/ # Multimodal (image/audio) tests +├── operators/ # CUDA/Triton operator tests +├── output/ # Output processing/LogProbs tests +├── platforms/ # Platform-specific tests +├── plugins/ # Plugin system tests +├── pooling/ # Prefix pooling tests +├── quantization/ # Quantization tests (W4A/W8A16/FP8) +├── reasoning/ # ERNIE/PaddleOCR/Qwen reasoning tests +├── router/ # Request routing tests +├── scheduler/ # Request scheduler tests +├── spec_decode/ # Speculative decoding tests +├── trace/ # Tracing/profiling tests +├── usage/ # Usage examples as tests +├── utils/ # Utility tests +├── v1/ # v1 API tests +├── worker/ # Worker process tests +├── xpu_ci/ # XPU-specific CI tests (excluded from coverage runs) +└── metax_ci/ # MetaX GPU CI tests (excluded from coverage runs) +``` +--- + +## CI Environment & Runner +### Runner Configuration +|Property|Value| +|-|-| +|**Runner label**|`GPU-h1z1-2Cards`| +|**Workflow**|`.github/workflows/_unit_test_coverage.yml`| +|**Timeout**|105 minutes total, 600s per test file| +|**Docker image**|`ccr-2vdh3abv-pub.cnc.bj.baidubce.com/paddlepaddle/paddleqa:fastdeploy-ciuse-cuda126-paddle-dev`| +|**GPUs**|2x NVIDIA H20 (dynamic port allocation)| + +### Environment Variables +The CI derives port variables from the runner name's last segment (GPU card ID): + +|Variable|Formula|Example (DEVICE_PORT=0)| +|-|-|-| +|`FD_API_PORT`|`8088 + DEVICE_PORT * 100`|8088| +|`FD_ENGINE_QUEUE_PORT`|`8058 + DEVICE_PORT * 100`|8058| +|`FD_METRICS_PORT`|`8078 + DEVICE_PORT * 100`|8078| +|`FD_CACHE_QUEUE_PORT`|`8098 + DEVICE_PORT * 100`|8098| +|`FD_ROUTER_PORT`|`8048 + DEVICE_PORT * 100`|8048| +|`FD_CONNECTOR_PORT`|`8038 + DEVICE_PORT * 100`|8038| +|`FD_RDMA_PORT`|`8028 + DEVICE_PORT * 100`|8028| +|`FLASK_PORT`|`8068 + DEVICE_PORT * 100`|8068| +|`MODEL_PATH`|Set to `/ModelData` (read-only mount)|-| +|`FD_LOG_DIR`|Auto-set per test to isolate logs|`unittest_logs///log`| + +> **Local defaults**: When env vars are unset locally, `serving_utils.py` uses different defaults (e.g. `FD_API_PORT=8188`). Always set these variables explicitly when running service tests locally. + +--- + +## Writing Strategy by Test Type + +FastDeploy tests fall into four distinct patterns based on what they exercise. Choose the pattern that matches the code under test. + +--- + +### Pattern 1 — Pure Logic / Data Structure Tests +**Where**: `engine/`, `scheduler/`, `router/`, `output/`, `reasoning/`, `logger/`, `trace/`, `usage/`, `platforms/`, `quantization/`, `model_executor/` (config classes, utils, tokenizers, non-GPU logic) + +These tests validate algorithms, config parsing, data classes, or error messages with no GPU or service dependency. + +- Use `unittest.TestCase` (most existing tests) or plain pytest (no base class needed) +- Use `unittest.mock.patch` / `MagicMock` to stub heavy dependencies (zmq, redis, subprocess, requests) +- Use `patch.dict("os.environ", ...)` for environment-variable-driven branches +- Assert error message content with `assertIn(str(ctx.exception))` +- **Config class gotcha**: When testing classes that inherit from base configs (e.g., `PretrainedConfig`), always assert against the *actual runtime value* after instantiation, not the default parameter in the function signature. Parent `__init__` calls may override child-set attributes (e.g., a second `super().__init__()` can reset `pad_token_id` to `None`). + +```python +import unittest +from unittest.mock import MagicMock, patch +from fastdeploy.module import TargetClass + + +class TestTargetClass(unittest.TestCase): + def test_valid_input(self): + obj = TargetClass(param="value") + self.assertEqual(obj.result(), expected) + + def test_invalid_input_raises(self): + with self.assertRaises(ValueError) as ctx: + TargetClass(param="bad") + self.assertIn("expected message fragment", str(ctx.exception)) + + @patch("fastdeploy.module.external_dep") + def test_with_mock(self, mock_dep): + mock_dep.return_value = MagicMock(data="test") + result = TargetClass().method() + self.assertIsNotNone(result) +``` + +--- + +### Pattern 2 — GPU Kernel / Numerical Accuracy Tests +**Where**: `layers/`, `operators/`, `batch_invariant/`, `spec_decode/`, `worker/`, `multimodal/`, `model_executor/` (GPU ops, kernels, numerical computations only) + +These tests run real GPU kernels and compare against a reference (numpy/paddle naive) implementation. + +- Use `paddle.set_device("gpu")` in `setUp` or at module level +- Mark with `@pytest.mark.gpu` so CI skips them on non-GPU machines +- Use `np.testing.assert_allclose(rtol=..., atol=...)` for float tolerance; `np.testing.assert_array_equal` for exact integer results +- Cover multiple shapes/dtypes with `@pytest.mark.parametrize` or nested loops + +```python +import numpy as np +import paddle +import pytest +from fastdeploy.model_executor.ops import my_kernel + + +@pytest.mark.gpu +class TestMyKernel: + def setup_method(self): + paddle.set_device("gpu") + + @pytest.mark.parametrize("shape", [(1024, 512), (4096, 256)]) + def test_matches_reference(self, shape): + x = paddle.randn(shape, dtype="float16") + ref = naive_numpy_impl(x.numpy()) + out = my_kernel(x).cast("float32").numpy() + np.testing.assert_allclose(out, ref, rtol=1e-3, atol=1e-3) +``` + +--- + +### Pattern 3 — Offline Inference Tests (Python API, real model) +**Where**: `entrypoints/`, `deterministic/`, `pooling/`, `model_loader/` + +These tests load a real model via `LLM(...)` or `ModelRegistry` and verify generation outputs or weight shapes. They require `MODEL_PATH` to be set. + +- Use `setUpClass` (unittest) or `@pytest.fixture(scope="module")` (pytest) to load the model once per test file +- Guard initialization with `unittest.SkipTest` or `pytest.skip` when `MODEL_PATH` is absent +- Do **not** import `fastdeploy` at module level — import inside the fixture or `setUpClass` to avoid CUDA initialization before fork +- Use `FD_ENGINE_QUEUE_PORT` / `FD_CACHE_QUEUE_PORT` from environment + +```python +import os +import unittest +from e2e.utils.serving_utils import FD_ENGINE_QUEUE_PORT, FD_CACHE_QUEUE_PORT + + +class TestOfflineInference(unittest.TestCase): + @classmethod + def setUpClass(cls): + model = os.path.join(os.getenv("MODEL_PATH", ""), "your-model") + try: + from fastdeploy.entrypoints.llm import LLM + from fastdeploy.engine.sampling_params import SamplingParams + cls.LLM = LLM + cls.SamplingParams = SamplingParams + cls.llm = LLM( + model=model, + engine_worker_queue_port=int(FD_ENGINE_QUEUE_PORT), + cache_queue_port=int(FD_CACHE_QUEUE_PORT), + ) + except Exception as e: + raise unittest.SkipTest(f"Model init failed: {e}") + + def test_basic_generation(self): + outputs = self.llm.generate(["Hello"], self.SamplingParams(max_tokens=32)) + self.assertEqual(len(outputs), 1) +``` + +--- + +### Pattern 4 — Online Serving / E2E Tests (subprocess + HTTP) +**Where**: `e2e/`, `distributed/` + +These tests start the FastDeploy API server (or a distributed job) as a subprocess and interact over HTTP. See existing files under `tests/e2e/` for full examples. + +- Use `@pytest.fixture(scope="session", autouse=True)` to start/stop the server once per file +- Launch with `subprocess.Popen(..., start_new_session=True)` and redirect output to `server.log` +- Poll `is_port_open("127.0.0.1", FD_API_PORT)` up to 10 minutes before declaring startup failure +- Tear down with `os.killpg(process.pid, signal.SIGTERM)` + `clean_ports()` +- Use `requests.post` for HTTP validation; assert `status_code` and response fields +- For distributed tests (`tests/distributed/`): launch via `paddle.distributed.launch` subprocess; assert `returncode == 0` + +```python +import os, signal, subprocess, sys, time +import pytest, requests +from e2e.utils.serving_utils import ( + FD_API_PORT, FD_CACHE_QUEUE_PORT, FD_ENGINE_QUEUE_PORT, FD_METRICS_PORT, + clean_ports, is_port_open, +) + + +@pytest.fixture(scope="session", autouse=True) +def server(): + clean_ports() + model_path = os.path.join(os.getenv("MODEL_PATH", "."), "your-model") + cmd = [ + sys.executable, "-m", "fastdeploy.entrypoints.openai.api_server", + "--model", model_path, + "--port", str(FD_API_PORT), + "--engine-worker-queue-port", str(FD_ENGINE_QUEUE_PORT), + "--metrics-port", str(FD_METRICS_PORT), + "--cache-queue-port", str(FD_CACHE_QUEUE_PORT), + "--tensor-parallel-size", "1", + "--max-model-len", "4096", + "--max-num-seqs", "32", + ] + with open("server.log", "w") as log: + process = subprocess.Popen(cmd, stdout=log, stderr=subprocess.STDOUT, + start_new_session=True) + for _ in range(10 * 60): + if is_port_open("127.0.0.1", FD_API_PORT): + break + time.sleep(1) + else: + os.killpg(process.pid, signal.SIGTERM) + raise RuntimeError(f"Server did not start on port {FD_API_PORT}") + yield + os.killpg(process.pid, signal.SIGTERM) + clean_ports() + + +@pytest.fixture(scope="session") +def api_url(): + return f"http://0.0.0.0:{FD_API_PORT}/v1/chat/completions" + + +def test_basic_generation(api_url): + resp = requests.post(api_url, + json={"messages": [{"role": "user", "content": "Hello"}], "max_tokens": 32}, + headers={"Content-Type": "application/json"}) + assert resp.status_code == 200 + assert resp.json()["choices"][0]["message"]["content"] +``` + +--- + +## Port Management +Tests that reference any port variable are automatically classified as multi-GPU (sequential). + +### Port Variables +Port variables are read from environment. CI injects them automatically per GPU card; `serving_utils.py` provides fallback defaults when running locally: + +```python +from e2e.utils.serving_utils import FD_API_PORT, FD_ENGINE_QUEUE_PORT, FD_METRICS_PORT, FD_CACHE_QUEUE_PORT +``` + +### clean_ports() +`clean_ports()` (from `e2e.utils.serving_utils`) kills processes on the above ports and cleans unix sockets. Call it manually only for tests that don't launch a server via subprocess. + +```python +from e2e.utils.serving_utils import clean_ports + +clean_ports() +``` + +--- + +## Coverage Requirements +### PR Coverage +- **Threshold**: 80% diff coverage +- **Tool**: `diff-cover` with `--fail-under=80` +- **Output**: `diff_coverage.json` uploaded to BOS + +### Coverage Configuration +- **Config**: `scripts/.coveragerc` +- **Data**: `coveragedata/.coverage` +- **Report**: `python_coverage_all.xml` + +### Running Coverage Locally +```bash +# Install requirements +pip install -r scripts/unittest_requirement.txt + +# Set coverage config +export COVERAGE_FILE=coveragedata/.coverage +export COVERAGE_RCFILE=scripts/.coveragerc + +# Run single test with coverage +python -m coverage run -m pytest tests/engine/test_engine.py -vv + +# Run with --source to limit coverage scope (must be a directory path, NOT a dotted module name) +python -m coverage run --source=fastdeploy/model_executor/models/paddleocr_vl -m pytest tests/model_executor/test_paddleocr_vl_config.py -vv + +# Generate report (with per-line missing info) +coverage combine coveragedata/ || echo "No data to combine" +coverage report -m +``` + +> **Note**: The `--source` parameter accepts directory paths (e.g., `fastdeploy/engine`) or top-level package names (e.g., `fastdeploy`). It does NOT accept dotted module paths like `fastdeploy.engine.module` or file paths like `fastdeploy/engine/module.py` — these will silently produce no coverage data. +--- + +## pytest Configuration +### Markers +Tests can be marked with `@pytest.mark.gpu`: + +```python +@pytest.mark.gpu +def test_gpu_feature(self): + """Test that requires GPU.""" + ... +``` +The `conftest.py` hook automatically skips GPU-marked tests on non-GPU platforms (detected via `/dev/nvidia[0-9]*`). + +### pytest.ini (cov_pytest.ini) +``` +[pytest] +addopts = + --ignore=tests/ci_use + --ignore=tests/ci_validation + --ignore=tests/operators/test_fused_moe.py + --ignore=tests/operators/test_w4afp8_gemm.py + --ignore=tests/model_loader/test_w4a8_model.py + --ignore=tests/xpu_ci + --ignore=tests/metax_ci + --ignore=tests/e2e/4cards_cases + --ignore=tests/e2e/golang_router + --ignore=tests/v1/test_schedule_output.py + --ignore=tests/graph_optimization/test_cuda_graph_dynamic_subgraph.py +``` +--- + +## Test Execution Flow +### CI Execution (coverage_run.sh) +1. **Collect tests**: `pytest --collect-only` to find all `test_*.py` files +2. **Classify tests**: Separate into `multi_gpu` and `single_gpu` based on rules +3. **Run multi-GPU tests**: Sequentially on GPU 0 and GPU 1 +4. **Run single-GPU tests**: Split into 2 shards, run in parallel (1 per GPU) +5. **Combine coverage**: Merge coverage data from all shards +6. **Generate reports**: XML coverage + diff coverage for PRs +7. **Upload results**: Upload to BOS storage +8. **Check threshold**: Fail if diff coverage < 80% (exit code 9) + +### Local Execution +```bash +# Run all tests +pytest tests/ -vv + +# Run specific test directory +pytest tests/engine/ -vv + +# Run with coverage +python -m coverage run -m pytest tests/engine/test_engine.py -vv +python -m coverage report + +# Run with timeout (same as CI) +timeout 600 python -m coverage run -m pytest tests/engine/test_engine.py -vv +``` +--- + +## Common Patterns +### Async Tests +```python +import pytest +import asyncio + + +class TestAsyncFeature: + @pytest.mark.asyncio + async def test_async_operation(self): + """Test async functionality.""" + result = await async_function() + assert result is not None +``` +### Parameterized Tests +```python +import pytest + + +class TestParameterized: + @pytest.mark.parametrize("input,expected", [ + ("test1", "result1"), + ("test2", "result2"), + ]) + def test_parameterized(self, input, expected): + """Test with multiple inputs.""" + result = process(input) + assert result == expected +``` +### Mock Tests +```python +from unittest.mock import Mock, patch + + +class TestWithMock: + @patch("fastdeploy.module.external_api") + def test_with_external_mock(self, mock_api): + """Test with mocked external dependency.""" + mock_api.return_value = Mock(data="test_data") + result = function_using_api() + assert result == expected +``` +--- + +## Error Handling & Logging +### Isolated Log Directory +The CI automatically sets `FD_LOG_DIR` per test: + +```python +import os + +log_dir = os.environ.get("FD_LOG_DIR", "log") +``` +### Error Logging +Failed tests automatically capture error logs via `pytest_runtest_makereport` hook in `conftest.py`. Logs are saved to `FD_LOG_DIR/pytest__error.log`. + +### Retry on OOM +The CI script automatically retries tests killed by OOM (exit code 137) up to 3 times. + +--- + +## Checklist +Before submitting a test: + +- [ ] File name follows `test_.py` pattern +- [ ] Test class follows `Test` pattern +- [ ] Test methods follow `test_` pattern +- [ ] Uses pytest (or unittest) +- [ ] Located in appropriate `tests/` subdirectory +- [ ] Service tests use a session-scoped fixture to start/stop the server subprocess; see `tests/e2e/` for reference patterns +- [ ] Any test referencing port variables or TP config will run sequentially in CI — this includes `tensor_parallel_size=1` +- [ ] Cleans up resources: server subprocess terminated with `os.killpg` + `clean_ports()` in fixture teardown +- [ ] Has `if __name__ == "__main__": pytest.main(...)` or `unittest.main()` for local execution +- [ ] Does not exceed 600s timeout per test file +- [ ] Maintains or improves coverage (80% diff threshold for PRs) diff --git a/.claude/skills/write-fastdeploy-unittest/debug.sh b/.claude/skills/write-fastdeploy-unittest/debug.sh new file mode 100644 index 00000000000..0cc09ff5977 --- /dev/null +++ b/.claude/skills/write-fastdeploy-unittest/debug.sh @@ -0,0 +1,2 @@ +#!/bin/bash +# just for testing diff --git a/.github/workflows/check-bypass.yml b/.github/workflows/check-bypass.yml index c9256e7a6cf..7ac75504218 100644 --- a/.github/workflows/check-bypass.yml +++ b/.github/workflows/check-bypass.yml @@ -69,6 +69,9 @@ jobs: can_skip_docs=true for f in $files; do + if [[ "$f" =~ ^\.claude/ || "$f" =~ ^benchmarks/ || "$f" =~ ^docs/ || "$f" =~ ^dockerfiles/ || "$f" =~ ^examples/ || "$f" =~ ^tools/ ]]; then + continue + fi if [[ ! "$f" =~ \.(md|yaml|go)$ ]]; then can_skip_docs=false break From 592a1a3503462dc1f2358c45a3319cfcde816572 Mon Sep 17 00:00:00 2001 From: EmmonsCurse <1577972691@qq.com> Date: Wed, 20 May 2026 22:12:59 +0800 Subject: [PATCH 2/3] [CI] [DEBUG] Skip CI for non-runtime directories --- .github/workflows/check-bypass.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-bypass.yml b/.github/workflows/check-bypass.yml index 7ac75504218..d6354d8ed0a 100644 --- a/.github/workflows/check-bypass.yml +++ b/.github/workflows/check-bypass.yml @@ -72,7 +72,7 @@ jobs: if [[ "$f" =~ ^\.claude/ || "$f" =~ ^benchmarks/ || "$f" =~ ^docs/ || "$f" =~ ^dockerfiles/ || "$f" =~ ^examples/ || "$f" =~ ^tools/ ]]; then continue fi - if [[ ! "$f" =~ \.(md|yaml|go)$ ]]; then + if [[ ! "$f" =~ \.(md|yaml|go|yml)$ ]]; then can_skip_docs=false break fi From 7c2846f5053bb4aff304945e52be8b8efafa26cb Mon Sep 17 00:00:00 2001 From: EmmonsCurse <1577972691@qq.com> Date: Wed, 20 May 2026 22:26:34 +0800 Subject: [PATCH 3/3] [CI] add unittest Claude skill --- .claude/skills/write-fastdeploy-unittest/debug.sh | 2 -- .github/workflows/check-bypass.yml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) delete mode 100644 .claude/skills/write-fastdeploy-unittest/debug.sh diff --git a/.claude/skills/write-fastdeploy-unittest/debug.sh b/.claude/skills/write-fastdeploy-unittest/debug.sh deleted file mode 100644 index 0cc09ff5977..00000000000 --- a/.claude/skills/write-fastdeploy-unittest/debug.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -# just for testing diff --git a/.github/workflows/check-bypass.yml b/.github/workflows/check-bypass.yml index d6354d8ed0a..7ac75504218 100644 --- a/.github/workflows/check-bypass.yml +++ b/.github/workflows/check-bypass.yml @@ -72,7 +72,7 @@ jobs: if [[ "$f" =~ ^\.claude/ || "$f" =~ ^benchmarks/ || "$f" =~ ^docs/ || "$f" =~ ^dockerfiles/ || "$f" =~ ^examples/ || "$f" =~ ^tools/ ]]; then continue fi - if [[ ! "$f" =~ \.(md|yaml|go|yml)$ ]]; then + if [[ ! "$f" =~ \.(md|yaml|go)$ ]]; then can_skip_docs=false break fi