diff --git a/bin/gstack-retro-metrics b/bin/gstack-retro-metrics index 58c2c05180..4f9b5b219e 100755 --- a/bin/gstack-retro-metrics +++ b/bin/gstack-retro-metrics @@ -297,10 +297,14 @@ echo "PRS_REFERENCED: $(printf '%s' "$_PRS" | wc -w | tr -d ' ')" [ -n "$_PRS" ] && echo "PR_REFS: $_PRS" || true # ── Test health (repo-wide + window) ───────────────────────────────────────── -# Deliberately suffix-only (narrower than is_test's dir-based patterns): the -# repo-wide census counts conventional test FILES; is_test additionally counts -# dir-homed helpers toward test-insertion ratios. -_TF_TOTAL=$(git ls-files 2>/dev/null | grep -cE '(\.test\.|\.spec\.|_test\.|_spec\.)' || true) +# Deliberately filename-convention-only (narrower than is_test's dir-based +# patterns): the repo-wide census counts conventional test FILES; is_test +# additionally counts dir-homed helpers toward test-insertion ratios. +# Suffix forms cover JS/TS (.test./.spec.), Go (_test.), Ruby (_spec.). +# The trailing prefix form covers pytest/unittest and minitest, whose default +# discovery is `test_*.py` / `test_*.rb` -- a filename convention, not a +# directory one, so it does not readmit dir-homed helpers like tests/utils.py. +_TF_TOTAL=$(git ls-files 2>/dev/null | grep -cE '(\.test\.|\.spec\.|_test\.|_spec\.|(^|/)test_[^/]*\.(py|rb)$)' || true) case "$_TF_TOTAL" in ''|*[!0-9]*) _TF_TOTAL=0 ;; esac echo "TEST_FILES_TOTAL: $_TF_TOTAL" _REG=$(git log "$REF" "$_S" ${_U:+"$_U"} --oneline --grep="test(qa):" --grep="test(design):" --grep="test: coverage" 2>/dev/null || true) diff --git a/test/gstack-retro-metrics.test.ts b/test/gstack-retro-metrics.test.ts index c1cf2450cf..10018b2251 100644 --- a/test/gstack-retro-metrics.test.ts +++ b/test/gstack-retro-metrics.test.ts @@ -306,6 +306,37 @@ describe('gstack-retro-metrics edges', () => { } }); + // Regression: the repo-wide census was suffix-only, so pytest/unittest's + // default `test_*.py` discovery name counted as zero. A Python repo whose + // whole suite is `tests/test_*.py` reported TEST_FILES_TOTAL: 0 while its + // TEST_RATIO was healthy -- the two lines disagreed and the low one was + // silently wrong. Dir-homed helpers (tests/utils.py) must still NOT count. + test('counts pytest-style test_*.py and minitest test_*.rb, not dir-homed helpers', () => { + const pyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-rm-py-')); + try { + git(pyDir, ['init', '-b', 'main']); + git(pyDir, ['config', 'user.email', 'py@example.com']); + git(pyDir, ['config', 'user.name', 'Py']); + fs.mkdirSync(path.join(pyDir, 'tests'), { recursive: true }); + fs.mkdirSync(path.join(pyDir, 'app'), { recursive: true }); + write(pyDir, 'tests/test_login.py', 'def test_login():\n assert True\n'); + write(pyDir, 'tests/test_logout.py', 'def test_logout():\n assert True\n'); + write(pyDir, 'tests/helper_test.rb', "require 'minitest'\n"); + write(pyDir, 'tests/test_signup.rb', "require 'minitest'\n"); + // Must NOT count: a dir-homed helper, and a source file merely prefixed + // with "test" but not "test_". + write(pyDir, 'tests/utils.py', 'HELPER = 1\n'); + write(pyDir, 'app/testing.py', 'MODE = 1\n'); + commit(pyDir, 'test: add suite', '2026-03-10T09:00:00'); + const out = runMetrics(['--base', 'main', '--since', '2026-03-09T00:00:00'], pyDir); + // test_login.py, test_logout.py, test_signup.rb (prefix) + helper_test.rb (suffix) = 4 + expect(out).toMatch(/^TEST_FILES_TOTAL: 4$/m); + expect(out).toMatch(/^RETRO_METRICS_END: ok$/m); + } finally { + fs.rmSync(pyDir, { recursive: true, force: true }); + } + }); + test('local reads only: no network git ops or curl anywhere in the script', () => { const script = fs.readFileSync(SCRIPT, 'utf-8'); expect(script).not.toMatch(/(^|[;|&`($!]|\s)git(\s+-C\s+\S+)?\s+(push|pull|fetch|clone|ls-remote)\b/m);