Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Device Test Runner Ci
name: Device Test Runner CI

on:
push:
Expand All @@ -8,29 +8,39 @@ on:
branches:
- main

permissions:
contents: read

jobs:
test:
name: Code Quality and Tests
runs-on: ubuntu-latest
steps:
- name: Checkout respository
- name: Checkout repository
uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.12"
python-version: "3.14"

- name: Install Poetry
run: pip install poetry
run: python -m pip install "poetry>=2,<3"

- name: Show Python version
run: python --version

- name: Show Poetry version
run: poetry --version

- name: Check dependency lock file
run: poetry check --lock

- name: Install dependencies
run: poetry install
run: poetry install --no-interaction

- name: Code Quality
run: poetry run pre-commit run --all-files --show-diff-on-failure

- name: Run tests
run: poetry run pytest
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ __pycache__/

.DS_Store
.pytest_cache/
.ruff_cache/

# Virtual environments
.venv/
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,38 @@
- Edited versioned documentation for clearer headings, shorter explanations and readable acceptance conditions.
- Expanded the commit checklist with concrete documentation and release checks.

## [1.6.1]

Version scope: Safe Process Termination. Runtime is 1.6.1; distribution remains 1.6.0. This entry describes source changes, not a verified release.

### Added

- ProcessTerminator sends SIGTERM to an attempt process group, then SIGKILL after the grace period when needed.
- First SIGINT requests cancellation; the second raises KeyboardInterrupt. CLI run results use exit codes 0, 1 and 130.
- Real parent/child/grandchild tests cover timeout, cancellation, ignored SIGTERM, reader completion and saved stdout/stderr.
- Retry fixtures verify previous parent and child PIDs are absent when attempt two starts.
- Dedicated SIGINT handler tests and v1.6.1 architecture, test matrix, acceptance criteria and definition of done.

### Changed

- Executor starts a separate session for each attempt and requires an injected ProcessTerminator.
- Each output reader join has a two-second limit; cleanup failures can report PROCESS_ERROR or propagate if recovery also fails.
- Runtime and report metadata identify 1.6.1; result schema is unchanged from 1.6.0.
- Added Given/When/Then docstrings to 12 tests without changing their executable AST.

### Fixed

- Cleanup waits for the process group rather than treating parent exit as proof that descendants stopped.
- PermissionError during group probing no longer aborts the wait immediately.
- Retry tests use the correct fixture path, command quoting, lifecycle type, integer counter and artifact directory field.
- Executor timeout tests model the injected terminator; runner test log paths serialize as strings.

### Verification and limitations

- Local evidence and exact commands are recorded in [v1.6.1 Definition of Done](docs/definition_of_done/definition_of_done_v1.6.1.md).
- Detached descendants, normal completion with surviving children, Linux CI and end-to-end CLI signals remain unverified. SIGTERM is not wired to cancellation.
- Package version synchronization, tag, issue/milestone status and GitHub Release publication remain pending.

## [1.6.0]

Version scope: Cancellation Foundation. Runtime implementation is present; release tag and publication are not verified.
Expand Down
30 changes: 29 additions & 1 deletion CommitManual.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [ ] 在 [CHANGELOG](CHANGELOG.md) 記錄使用者可觀察的變更。
- [ ] 更新完成條件,區分已驗證與尚未完成的項目。
- [ ] 更新測試矩陣,讓需求能對應到測試。
- [ ] 更新 [測試指南](docs/test_guide.md) 的 pytest 用法大綱與對應版本案例;AI 新增或修改測試所使用的技巧也需納入。
- [ ] 更新驗收條件,寫出可確認的預期行為。
- [ ] 更新 [Roadmap](docs/roadmap.md),同步已實作功能與後續規劃。
- [ ] 保留舊版文件的歷史內容;新版本依既有語言、標題、檔名與 Markdown 格式撰寫。
Expand Down Expand Up @@ -49,7 +50,8 @@ Codex 必須先完整閱讀本清單,並核對下列文件。`<version>` 代
| --- | --- |
| `README.md` | 使用方式、範例與限制 |
| `docs/architecture/architecture_v<version>.md` | 架構、介面、資料流,以及符合該版本實作的 UML 類別圖與循序圖 |
| `docs/test_matrix/test_matrix_v<version>.md` | 測試覆蓋與證據 |
| `docs/test_matrix/test_matrix_v<version>.md` | 測試覆蓋、證據與 AI 協作新增或補強的案例,連結至測試指南的對應版本 |
| `docs/test_guide.md` | Pytest 用法大綱、工具分類、版本索引與各版實際案例 |
| `docs/acceptance_criteria/acceptance_criteria_v<version>.md` | 驗收條件與結論 |
| `docs/definition_of_done/definition_of_done_v<version>.md` | 完成狀態與待辦項目 |
| `docs/roadmap.md` | 版本進度與後續規劃 |
Expand All @@ -58,6 +60,32 @@ Codex 必須先完整閱讀本清單,並核對下列文件。`<version>` 代

文件任務只在需要時修改測試說明,不應為了更新文件改變產品行為。功能、測試數量、結果、tag、release 與 issue 狀態都必須有證據。未確認的發佈項目保持未勾選;Git tag 的存在不能證明 GitHub Release 已發布。

### 測試指南與 test matrix 的分工

[測試指南](docs/test_guide.md) 集中說明測試怎麼寫、使用哪些 pytest 工具;各版 test matrix 說明測試涵蓋哪些需求、修正與限制。教學內容放在指南,矩陣連結至指南的對應版本,避免重複維護。

AI 更新測試文件時,需同步核對以下項目:

- 維護指南的用法大綱與版本索引,依實際使用情況整理函式、fixture、Mock/Fake、參數化與執行方式。
- 各版說明依對應 Git tag、commit 或明確工作版本撰寫,不將最新版用法套回舊版;保留歷史結果與日期。
- 共通用法集中說明,各版只記錄差異與實際測試案例,避免重複教學。
- 區分框架提供的工具、專案輔助程式與示意範例;未使用的功能不列為已實作,範例不算測試證據。
- 新增版本或測試技巧時更新指南;只為補充說明時,不改變產品與測試行為。

#### AI 協作案例與修正紀錄

- 將 AI 新增、補強或修正的案例納入矩陣,分別寫出測試名稱、原本缺口、主要斷言與仍未涵蓋的情境。有協作紀錄才標示來源,不憑程式風格推測作者。
- 重要修正需說明「原本為何失敗 → 修改了什麼 → 哪個測試防止再次發生」。區分產品程式的問題與測試資料、Mock 設定或路徑錯誤。
- 說明測試的驗證範圍與限制,具體做法記錄在對應版本的 test matrix。
- 保留既有程式風格,以容易閱讀的註解與範例解釋必要概念;不為了補文件改寫測試行為。

#### 文件檢查

- [ ] 測試路徑、函式名稱與 API 用法已對照該版本原始碼。
- [ ] 範例語法、Markdown code fence、連結與執行指令已檢查。
- [ ] 執行指令與實際執行結果分開記錄;未執行的測試不寫成通過。
- [ ] 已說明模擬與真實操作的邊界,沒有把單元測試擴大解讀成端到端保證。

### 驗證與提交

1. 修改前執行 `pytest -q`,確認基準測試通過。
Expand Down
56 changes: 26 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

Device Test Runner 使用 YAML 定義裝置測試流程,執行既有的 Bash、Python、ADB 等命令,並保存每次執行的輸出與 JSON 報告。它負責安排測試步驟、驗證輸出檔案,以及依設定重試失敗步驟;裝置操作仍由你的腳本處理。

目前版本為 **v1.6.0**,支援五階段測試流程、步驟逾時、選擇性重試、必要與選用的輸出檔案驗證,以及 Python API 取消。Recorder 管理與遠端執行仍在規劃中。
目前 runtime/報告版本為 **v1.6.1**,支援五階段測試流程、步驟逾時、選擇性重試、輸出檔案驗證、程序群組清理與 Ctrl+C 取消。`pyproject.toml` 仍為 `1.6.0`,套件版本同步與發佈確認列於 [完成條件](docs/definition_of_done/definition_of_done_v1.6.1.md)。Recorder 管理與遠端執行仍在規劃中。

## 安裝

需要 Python 3.10+ 與 Poetry 2.x。在專案目錄執行:
需要 Python 3.10+ 與 Poetry 2.x。程序群組清理使用 POSIX signal API;目前本機驗證為 macOS,沒有 Windows 支援驗證。在專案目錄執行:

```bash
git clone git@github.com:andrew771027/DeviceTestRunner.git
Expand Down Expand Up @@ -77,7 +77,7 @@ global_teardown
| `teardown` | 記錄失敗但繼續執行該 stage 的剩餘 steps,之後執行 `global_teardown` |
| `global_teardown` | 記錄失敗但繼續執行該 stage 的剩餘 steps |

上述為一般失敗路由。v1.6.0 進入 setup 區塊後,即使 setup/scenario 取消,仍執行兩個 cleanup stages;run 開始前或 global_setup 取消時只執行 global_teardown。如果 global_setup 成功後、進入 setup 區塊前已觀察到取消,也會跳過 teardown。Cleanup attempt 使用新的 token;這些是受控流程的 best effort,不涵蓋未處理 Python exception 或 KeyboardInterrupt。
上述為一般失敗路由。進入 setup 區塊後,即使 setup/scenario 取消,仍執行兩個 cleanup stages;run 開始前或 global_setup 取消時只執行 global_teardown。如果 global_setup 成功後、進入 setup 區塊前已觀察到取消,也會跳過 teardown。Cleanup attempt 使用新的 token;這些是受控流程的 best effort,不涵蓋未處理 Python exception 或 KeyboardInterrupt。

最終 `summary.status` 以 `CANCELLED` 優先;沒有取消時,failed step、skipped step 或 required artifact failure 任一存在即為 `FAILED`,其餘為 `PASSED`。

Expand Down Expand Up @@ -160,11 +160,13 @@ artifact:
min_rows: 1
```

`after_step` 將 validation rule 綁定到指定 step,runner 會在該 step 每次 command 成功後立即驗證。`required` 預設為 `true`:required rule 失敗會使 attempt 失敗,且只有 failure type 出現在 `retry.retry_on`、尚未達 `max_attempts` 時才重試;`required: false` 的失敗仍寫入 report,但不影響 step 或 run 狀態。Lifecycle 結束後會再次對所有規則執行 final validation,包括有 `after_step` 的規則。YAML 未設定 `retry_on` 時預設為空清單,因此不重試;`none`、`cancelled` 與未知值會被拒絕。直接使用 Python `RetryConfig()` 的預設清單不同,詳見 v1.6.0 Architecture。
`after_step` 將 validation rule 綁定到指定 step,runner 會在該 step 每次 command 成功後立即驗證。`required` 預設為 `true`:required rule 失敗會使 attempt 失敗,且只有 failure type 出現在 `retry.retry_on`、尚未達 `max_attempts` 時才重試;`required: false` 的失敗仍寫入 report,但不影響 step 或 run 狀態。Lifecycle 結束後會再次對所有規則執行 final validation,包括有 `after_step` 的規則。YAML 未設定 `retry_on` 時預設為空清單,因此不重試;`none`、`cancelled` 與未知值會被拒絕。直接使用 Python `RetryConfig()` 的預設清單不同,詳見 [v1.6.0 的設定說明](docs/architecture/architecture_v1.6.0.md)。

## 透過 Python API 取消

取消由呼叫端持有 token 並呼叫 `cancel()`;`main.py` 尚未將 Ctrl+C/SIGTERM 轉為 token cancellation。以下示範沿用載入的 configuration,在兩秒後提出取消請求:
CLI 第一次 Ctrl+C/SIGINT 會呼叫 `token.cancel()`;第二次會拋出 `KeyboardInterrupt`,可能中斷 cleanup 與報告寫入。CLI 正常完成回傳 0、FAILED 回傳 1、CANCELLED 或 run 期間的 KeyboardInterrupt 回傳 130。SIGTERM 尚未接到 token。

Python API 由呼叫端持有 token 並呼叫 `cancel()`。以下示範沿用載入的 configuration,在兩秒後提出取消請求:

```python
from pathlib import Path
Expand All @@ -176,13 +178,18 @@ from runner.cancellation import CancellationToken
from runner.config import ConfigLoader
from runner.executor import SubprocessExecutor
from runner.failure import FailureClassifier
from runner.process import ProcessTerminator
from runner.reporter import JsonReporter
from runner.runner import DeviceTestRunner

config = ConfigLoader().load("configs/sample.yaml")
classifier = FailureClassifier()
runner = DeviceTestRunner(
executor=SubprocessExecutor(Path.cwd(), classifier),
executor=SubprocessExecutor(
project_directory=Path.cwd(),
failure_classifier=classifier,
process_terminator=ProcessTerminator(),
),
artifact_manager=ArtifactManager(config.artifact.output_dir),
artifact_validator=ArtifactValidator(),
failure_classifier=classifier,
Expand All @@ -200,9 +207,11 @@ finally:
timer.join()
```

Executor 以 0.1 秒 polling 檢查取消與 timeout,對直接子程序 terminate、等待兩秒後必要時 kill。尚未處理整棵 process tree,後代程序持有 stdout/stderr pipe 時,完成時間可能延長;上述 timer 不是兩秒內返回的保證。Cancelled attempt 不做 attempt validation 或 retry,但 run 最後仍驗證所有 artifacts。
Executor 每 0.1 秒檢查取消與 timeout。每次 attempt 建立獨立 session,清理時對 process group 送 SIGTERM,預設等待 2 秒;仍有程序則送 SIGKILL,再等最多 2 秒。stdout/stderr reader 各有 2 秒 join 上限。上述 timer 不是兩秒內返回的保證。

同群組的 child/grandchild 已有清理測試;自行脫離群組的程序,以及直接 process 正常退出後留下的背景程序,不在目前保證內。詳見 [Process Lifecycle](docs/process_lifecycle.md)。Cancelled attempt 不做 attempt validation 或 retry,但 run 最後仍驗證所有 artifacts。

升級至 v1.6.0 時,Python 呼叫端需調整 `SubprocessExecutor.execute(..., cancellation_token=...)`,以及手動建構 result dataclasses 時的新欄位。`run(config)` 仍可不傳 token。完整差異見 [Architecture](docs/architecture/architecture_v1.6.0.md)。
從 v1.6.0 升級時,`SubprocessExecutor` 建構子需新增 `process_terminator`。從更舊版本升級時,Python 呼叫端也需調整 `SubprocessExecutor.execute(..., cancellation_token=...)`,以及手動建構 result dataclasses 時的新欄位。`run(config)` 仍可不傳 token。完整差異見 [Architecture](docs/architecture/architecture_v1.6.1.md)。

## 輸出檔案

Expand Down Expand Up @@ -258,7 +267,7 @@ artifacts/
"device_serial": "demo",
"device_product": "demo",
"device_build": "demo",
"runner_version": "1.6.0",
"runner_version": "1.6.1",
"started_at": "2026-09-12T00:00:00+00:00",
"finished_at": "2026-09-12T00:00:01+00:00",
"cancel_requested": false
Expand Down Expand Up @@ -333,22 +342,6 @@ DeviceTestRunner
result.json / per-attempt logs / validation results
```

Device Test Runner 的核心資料流:

```text
Config
↓
Runner
↓
Executor
↓
StepResult
↓
ArtifactManager
↓
RunResult / result.json
```

## 執行專案測試

執行所有測試:
Expand Down Expand Up @@ -383,6 +376,8 @@ poetry run pytest -m artifact

歷史驗證紀錄(2026-09-12,Python 3.14):`.venv/bin/python -m pytest -q` → **153 passed in 39.39s**。150 個測試函式皆有 Given/When/Then 說明;參數化後共 153 個案例。

本次 v1.6.1 驗證與 162 個測試函式/168 個案例的對照,見 [完成條件](docs/definition_of_done/definition_of_done_v1.6.1.md) 與 [測試矩陣](docs/test_matrix/test_matrix_v1.6.1.md)。

## 持續整合

[CI workflow](.github/workflows/ci.yml) 會在 push 至 `main` 或建立以 `main` 為目標的 pull request 時執行。流程安裝 Python、Poetry 與專案依賴後,執行 pytest。
Expand All @@ -406,7 +401,7 @@ Repository secret 名稱為 `OPENAI_API_KEY`,在 CLI invocation 映射成 `COD

目前先補齊單機執行與取消流程,再加入可重用設定與 recorder 管理:

1. v1.6.1:程序群組終止、輸出串流收尾與 CLI 取消訊號。
1. v1.6.1:已實作程序群組終止、輸出串流收尾與 SIGINT handler;平台驗證、版本同步與發佈待完成。
2. v1.6.2:整次 run 的逾時設定。
3. v1.6.3:取消後的清理範圍、時間限制與部分結果保存。
4. v1.7.0~v1.7.2:YAML 靜態變數、環境變數與執行資訊。
Expand All @@ -419,9 +414,10 @@ Repository secret 名稱為 `OPENAI_API_KEY`,在 CLI invocation 映射成 `COD

| 文件 | 用途 |
| --- | --- |
| [架構 v1.6.0](docs/architecture/architecture_v1.6.0.md) | 元件、取消路由、報告欄位與相容性 |
| [測試矩陣 v1.6.0](docs/test_matrix/test_matrix_v1.6.0.md) | 功能對應的測試與覆蓋限制 |
| [驗收條件 v1.6.0](docs/acceptance_criteria/acceptance_criteria_v1.6.0.md) | 可觀察的預期行為 |
| [完成條件 v1.6.0](docs/definition_of_done/definition_of_done_v1.6.0.md) | 驗證紀錄與待完成的發佈項目 |
| [架構 v1.6.1](docs/architecture/architecture_v1.6.1.md) | 元件、取消路由、報告欄位與相容性 |
| [測試指南](docs/test_guide.md) | Pytest 工具、fixture、Mock 與各版用法 |
| [測試矩陣 v1.6.1](docs/test_matrix/test_matrix_v1.6.1.md) | 功能對應的測試與覆蓋限制 |
| [驗收條件 v1.6.1](docs/acceptance_criteria/acceptance_criteria_v1.6.1.md) | 可觀察的預期行為 |
| [完成條件 v1.6.1](docs/definition_of_done/definition_of_done_v1.6.1.md) | 驗證紀錄與待完成的發佈項目 |

`docs/` 內的舊版文件保留當時的設計與介面,使用時請確認版本。
2 changes: 1 addition & 1 deletion docs/acceptance_criteria/acceptance_criteria_v1.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Basic YAML runner.

Version baseline: Git tag `v1.0.0`

This document preserves the recorded acceptance state for this version. Test results and release checks below are historical records; they have not been rerun or reverified by this formatting update.
Historical acceptance record. Tests and release checks were not rerun for this editorial update.

## Acceptance criteria

Expand Down
2 changes: 1 addition & 1 deletion docs/acceptance_criteria/acceptance_criteria_v1.1.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Naming and model refactoring.

Version baseline: Git tag `v1.1.0`

This document preserves the recorded acceptance state for this version. Test results and release checks below are historical records; they have not been rerun or reverified by this formatting update.
Historical acceptance record. Tests and release checks were not rerun for this editorial update.

## Acceptance criteria

Expand Down
2 changes: 1 addition & 1 deletion docs/acceptance_criteria/acceptance_criteria_v1.2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Artifact management.

Version baseline: Git tag `v1.2.0`

This document preserves the recorded acceptance state for this version. Test results and release checks below are historical records; they have not been rerun or reverified by this formatting update.
Historical acceptance record. Tests and release checks were not rerun for this editorial update.

## Acceptance criteria

Expand Down
2 changes: 1 addition & 1 deletion docs/acceptance_criteria/acceptance_criteria_v1.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Test lifecycle.

Version baseline: Git tag `v1.3.0`

This document preserves the recorded acceptance state for this version. Test results and release checks below are historical records; they have not been rerun or reverified by this formatting update.
Historical acceptance record. Tests and release checks were not rerun for this editorial update.

## Acceptance criteria

Expand Down
2 changes: 1 addition & 1 deletion docs/acceptance_criteria/acceptance_criteria_v1.3.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Command output streaming.

Version baseline: Git tag `v1.3.5`

This document preserves the recorded acceptance state for this version. Test results and release checks below are historical records; they have not been rerun or reverified by this formatting update.
Historical acceptance record. Tests and release checks were not rerun for this editorial update.

## Acceptance criteria

Expand Down
2 changes: 1 addition & 1 deletion docs/acceptance_criteria/acceptance_criteria_v1.4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Artifact validation.

Version baseline: Git tag `v1.4.0`

This document preserves the recorded acceptance state for this version. Test results and release checks below are historical records; they have not been rerun or reverified by this formatting update.
Historical acceptance record. Tests and release checks were not rerun for this editorial update.

## Acceptance criteria

Expand Down
Loading
Loading