summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.github/scripts/ci_set_matrix.py20
-rw-r--r--test/hil/test/test_ci_metrics.py13
-rw-r--r--test/hil/test/test_ci_select.py36
3 files changed, 56 insertions, 13 deletions
diff --git a/.github/scripts/ci_set_matrix.py b/.github/scripts/ci_set_matrix.py
index ee3609bed..79f466893 100755
--- a/.github/scripts/ci_set_matrix.py
+++ b/.github/scripts/ci_set_matrix.py
@@ -127,12 +127,22 @@ def set_matrix_json(select=None):
if sel_fams is not None:
fams = [f for f in fams if f in sel_fams]
matrix[toolchain] = fams
- if sel_fams is not None:
- # a family this file does not list builds on no toolchain, so the selection maps
- # to an empty matrix and every leg skips - which looks exactly like a working
- # scoped run. Say so: hw/bsp holds several families CI has never built
- # (efm32, py32f0, ...) and espressif, whose boards are built by hil-build-esp
+ if sel_fams:
+ # a family this file does not list builds on no toolchain, so it contributes no
+ # leg. hw/bsp holds several CI has never built (efm32, py32f0, same7x, ...) plus
+ # espressif, whose boards hil-build-esp builds by name.
unbuilt = sorted(f for f in sel_fams if f not in family_list)
+ if unbuilt and not any(matrix.values()):
+ # NONE of the selected families is buildable here, so every leg would skip
+ # and the PR would go green from a build job that ran no compiler. That is
+ # an unusable selection, not "nothing selected": say UNSCOPED - which
+ # build.yml and .circleci/config.yml both grep for - and emit the full
+ # matrix. An explicit families: [] is still a legitimate nothing-selected,
+ # and a PARTIAL miss still scopes to the families that do build.
+ print(f'ci_set_matrix: UNSCOPED - no selected family is built by any '
+ f'toolchain here ({", ".join(unbuilt)}), emitting the full matrix',
+ file=sys.stderr)
+ return set_matrix_json(None)
if unbuilt:
print(f'ci_set_matrix: selected families built by no toolchain here: '
f'{", ".join(unbuilt)}', file=sys.stderr)
diff --git a/test/hil/test/test_ci_metrics.py b/test/hil/test/test_ci_metrics.py
index 6c236e827..a76b6e3a0 100644
--- a/test/hil/test/test_ci_metrics.py
+++ b/test/hil/test/test_ci_metrics.py
@@ -435,8 +435,17 @@ class TestWorkflowSelectionHandOff(unittest.TestCase):
self.assertIn('BUILD_SELECT_FILE', self.build)
scripts = os.path.join(os.path.dirname(CIRCLECI), '.github', 'scripts')
matrix = open(os.path.join(scripts, 'ci_set_matrix.py')).read()
- self.assertEqual(matrix.count('ci_set_matrix: UNSCOPED'), 2,
- 'every fall-open path must print the marker build.yml greps for')
+ # count-independent: pin the INVARIANT, not the number of fall-open paths -
+ # every message that emits the full matrix must carry the marker, and a purely
+ # informational note (a partial family miss) must not claim to have done so.
+ # Adjacent string literals are joined first, since these messages wrap.
+ import re as _re
+ flat = _re.sub(r"['\"]\s*\n\s*f?['\"]", '', matrix)
+ hits = [m.start() for m in _re.finditer('emitting the full ', flat)]
+ self.assertGreaterEqual(len(hits), 2, 'fall-open messages not found')
+ for i in hits:
+ self.assertIn('UNSCOPED', flat[max(0, i - 200):i],
+ 'a fall-open path without the marker build.yml greps for')
def test_membrowse_upload_sees_the_same_board_as_the_build(self):
# $EX_ARGS is passed for the BOARD it selects: --one-first picks a board that can
diff --git a/test/hil/test/test_ci_select.py b/test/hil/test/test_ci_select.py
index f5c64ac1c..8f1841531 100644
--- a/test/hil/test/test_ci_select.py
+++ b/test/hil/test/test_ci_select.py
@@ -777,12 +777,15 @@ class TestOrphanInvariant(unittest.TestCase):
for v in vendors:
self.assertTrue(ci_select.mcu_families(v + '/x.c', REPO), f'{v}: resolves to no family')
- # hw/bsp families ci_set_matrix's family_list does not map to any toolchain. Before
- # scoping these were harmless - the matrix was always every family in family_list,
- # so a PR touching one of them still compiled the other 64. Now the selection
- # intersects to nothing and every leg skips, so a family landing here by accident is
- # a silent hole. espressif is deliberate: its boards are built by hil-build-esp,
- # keyed on board name rather than family.
+ # hw/bsp families ci_set_matrix's family_list does not map to any toolchain. Master
+ # gave a PR touching one of these no compile coverage either - none of the other 64
+ # families compiles same7x's board.h - so this is not new. What IS new is that the
+ # gap used to be masked by a full matrix and is now the whole answer, which is why
+ # ci_set_matrix treats a selection that intersects family_list to NOTHING as
+ # unusable (UNSCOPED -> full matrix) rather than emitting an all-empty one.
+ # espressif is here because hil-build-esp builds its boards by name rather than by
+ # family - though only on hathach/tinyusb: that job is gated on repository_owner,
+ # so on a fork an espressif-only PR builds nowhere.
UNBUILT_FAMILIES = {'cxd56', 'efm32', 'espressif', 'f1c100s', 'pic32mz', 'py32f0',
'same7x'}
@@ -1505,6 +1508,27 @@ class TestCiSetMatrix(unittest.TestCase):
self.assertEqual(json.loads(r.stdout), base)
self.assertIn('ci_set_matrix: UNSCOPED', r.stderr) # build.yml greps this
+ def test_families_no_toolchain_builds_falls_open(self):
+ # hw/bsp/same7x is real but in no toolchain's list, so scoping to it emits an
+ # all-empty matrix: every leg skips and the PR goes green from a build job that
+ # ran no compiler. Unusable, not "nothing selected" - and the marker matters,
+ # because that is what build.yml and CircleCI grep to drop the build extras too.
+ base = json.loads(self.run_matrix().stdout)
+ r = self.run_matrix('--select',
+ json.dumps({'build': {'full': False, 'families': ['same7x']}}))
+ self.assertEqual(r.returncode, 0, r.stderr)
+ self.assertEqual(json.loads(r.stdout), base)
+ self.assertIn('ci_set_matrix: UNSCOPED', r.stderr)
+
+ def test_a_partial_toolchain_miss_still_scopes(self):
+ # one buildable family is real coverage: scope to it and just note the other
+ r = self.run_matrix('--select', json.dumps(
+ {'build': {'full': False, 'families': ['stm32f4', 'same7x']}}))
+ self.assertEqual(r.returncode, 0, r.stderr)
+ self.assertEqual(json.loads(r.stdout)['arm-gcc'], ['stm32f4'])
+ self.assertNotIn('UNSCOPED', r.stderr)
+ self.assertIn('same7x', r.stderr)
+
def test_explicit_empty_families_selects_nothing(self):
# an explicit [] IS a legitimate answer (a diff that builds nothing)
r = self.run_matrix('--select',