Repository navigation
125 lines (117 loc) · 6.79 KB
/
Copy pathtools-python.yml
File metadata and controls
125 lines (117 loc) · 6.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# The Python test suites under Tools/, which nothing ran.
#
# `Tools/linux-capture` carries 212 tests across 8 files — the frame decoder, the HCI extractor, the
# activity and sync decoders, the spot-HRV path, and the SpO2 promote gate that #103 is waiting on. Every
# one of them only ever executed when a contributor remembered to, which meant a decoder could regress and
# the suite that would have caught it stayed green by never being run.
#
# Cheap to run: pure stdlib `unittest`, no dependencies, no fixtures to fetch, ubuntu rather than macOS —
# about 25 seconds end to end.
#
# NOT path-filtered, and that is deliberate. It used to fire only on `Tools/**.py`, on the premise that a
# suite can only break when its own source changes. That is false for `test_home_i18n.py`, which asserts
# on the CONTENTS of app and package files — AppModel.swift, the xcstrings catalogues, ReadinessEngine,
# DayNavBar. A Swift-only commit broke it (#1671 moved a sign and a hardcoded °C out of a localized
# literal) and the filter meant this job never ran, so `main` sat red until a contributor touching
# `Tools/` for something unrelated tripped over it in their own PR (#1691).
#
# Widening the filter to name those paths would just re-create the trap one edit later: the list is
# whatever the assertions happen to read today. Twenty-five seconds on every PR is cheaper than a gate
# that is green because it did not run.
#
# That paragraph was already here, and a `Tools/**` filter was reinstated underneath it anyway, on the
# Windows runner's cost. It cost the same thing a second time on 2026-09-24: an Android-only commit
# changed a padding `test_home_i18n.py` asserts on, this job did not run, and main carried a red suite
# through nine merges until a commit that happened to touch Tools/ finally tripped it. One of those
# merges was an outside contributor's.
#
# So the split below, rather than a third round of the same argument: the Linux leg runs on everything
# because it reads product source, and the Windows leg keeps its filter because it does not.
name: Tools Python CI
on:
# UNFILTERED, which is what the note above argues for and what the filter that used to sit here undid.
#
# The filter was added for a real reason: a Windows runner billed at twice a Linux minute should not run
# on the many pull requests that touch no Python. That reason was sound and the remedy was not, because
# it silenced the Linux leg too, and the Linux leg is the one that reads product source. The Windows leg
# now lives in tools-python-windows.yml and keeps a filter of its own, so the cost argument is answered
# where it applies instead of across a job it was never about.
#
# This leg is ubuntu and about 25 seconds. It stays on every pull request and every push to main.
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:
concurrency:
group: tools-python-${{ github.ref }}
cancel-in-progress: true
jobs:
ecg-ppg-reference:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install offline signal-analysis dependencies
run: pip install -r Tools/EcgPpgReference/requirements.txt
- name: Validate ECG/PPG experiment on synthetic signals
run: python3 Tools/EcgPpgReference/run_tests.py
# The workflow is unfiltered: tests, detector, dependencies and this gate
# all invalidate it. The runner requires the exact roster and no skips.
# This does not validate sensor hardware or the optional Swift HR control;
# PpgHr itself is covered by the separate Swift package test workflow.
linux-capture:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
# `discover` rather than a hand-listed set, so a NEW test file is picked up without editing this
# workflow — the failure mode that leaves a suite unrun is exactly what this job exists to end.
#
# The count assertion is the same idea one level up: `discover` exits 0 having found NOTHING, so a
# wrong working-directory or a renamed pattern would turn this job green while running no tests at
# all. Checking a floor makes that fail loudly instead. Raise the floor when suites are added; do
# not lower it to make a red run pass.
- name: Run Tools/linux-capture tests
run: |
set -o pipefail # GitHub's default shell is `bash -e` WITHOUT pipefail: piping into tee
# would return tee's status and swallow a failing suite entirely.
python3 -m unittest discover -p "test_*.py" -v 2>&1 | tee "$RUNNER_TEMP/out.txt"
ran=$(grep -oE '^Ran [0-9]+ test' "$RUNNER_TEMP/out.txt" | grep -oE '[0-9]+')
echo "collected ${ran:-0} tests"
if [ "${ran:-0}" -lt 200 ]; then
echo "::error::expected at least 200 tests, collected ${ran:-0} — discovery is broken, not the suite"
exit 1
fi
working-directory: Tools/linux-capture
# The Tools/ suites: the i18n audit that gates every PR's translations, and the What's New
# generator (#878). Neither ran before — i18n-coverage.yml invokes `i18n_audit.py --ci` but never
# its test file. `discover`, not named modules, for the same reason as above: naming them meant the
# generator's tests would have been added and silently never run. Run from inside Tools/ because
# the suites import their modules by bare name.
# The ONLY third-party dependency in this job. `appchangelog-gen.py` parses the release notes'
# YAML front matter and exits with a clear message when PyYAML is absent — which is right for a
# tool run by hand, and useless here: its test would have to skip the one case that matters, a
# broken front matter in the notes about to be released. Everything else in both suites is
# standard-library only, so this stays a single line rather than a requirements file.
- name: Install PyYAML (appchangelog-gen's only dependency)
run: pip install pyyaml
- name: Run Tools/ tests
run: |
set -o pipefail
mapfile -t files < <(find . -maxdepth 1 -name 'test_*.py' -printf '%f\n' | sort)
modules=("${files[@]%.py}")
python3 -m unittest -v "${modules[@]}" 2>&1 | tee "$RUNNER_TEMP/out.txt"
ran=$(grep -oE '^Ran [0-9]+ test' "$RUNNER_TEMP/out.txt" | grep -oE '[0-9]+')
echo "collected ${ran:-0} tests"
if [ "${ran:-0}" -lt 114 ]; then
echo "::error::expected at least 114 core Tools tests, collected ${ran:-0} — discovery is broken, not the suite"
exit 1
fi
working-directory: Tools