From 464e096c972a1146718f05b4ede4dc49588eeae1 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Mon, 12 Oct 2026 08:16:52 -0700 Subject: [PATCH] fix(ci): include target kind in test executable labels Library and integration targets can share a package and name. Include Cargo target kind at discovery so each executable has its own log, result name and timing key; keep scheduling and status aggregation unchanged. Cover distinct library/integration/bin identities, independent failure evidence, package filtering and timing priorities without launching Cargo. Fixes #137 Assisted-by: gpt-6.1-sol --- tools/ci.py | 3 +- tools/test_ci.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 tools/test_ci.py diff --git a/tools/ci.py b/tools/ci.py index 23da9832023b6b05fb20ae2f14a69247b7838365..5b209fcd11a1df5745a90fef200bc43fe8d9ea07 100644 --- a/tools/ci.py +++ b/tools/ci.py @@ -221,7 +221,8 @@ def run_tests(run, deadline, packages, jobs): if message.get('reason') == 'compiler-artifact' and message['profile']['test'] and message['executable']: folder = Path(message['manifest_path']).parent if packages is None or folder.name in packages: - binaries.append((f'{folder.name}.{message["target"]["name"]}', [message['executable']], folder)) + binaries.append((f'{folder.name}.{message["target"]["name"]}.' + f'{"-".join(message["target"]["kind"])}', [message['executable']], folder)) binaries.append(('doctests', ['cargo', 'test', '--locked', '--workspace', '--all-features', '--doc'], CI)) # The slowest last time start first, so that none of them starts last. timings = RUNS / 'test-seconds.json' diff --git a/tools/test_ci.py b/tools/test_ci.py new file mode 100644 index 0000000000000000000000000000000000000000..50852c75faf29f83653ba85de2db6f2d6266db06 --- /dev/null +++ b/tools/test_ci.py @@ -0,0 +1,73 @@ +from importlib.util import module_from_spec, spec_from_file_location +import json +from pathlib import Path +from tempfile import TemporaryDirectory +import unittest +from unittest.mock import patch + + +spec = spec_from_file_location('ci', Path(__file__).with_name('ci.py')) +ci = module_from_spec(spec) +spec.loader.exec_module(ci) + + +class TestExecutableIdentity(unittest.TestCase): + def test_same_named_library_and_integration_test_keep_separate_results(self): + with TemporaryDirectory() as temporary: + root = Path(temporary) + artifacts = [] + for package, name, kind, executable in [ + ('relay', 'relay', 'test', '/tests/relay-integration'), + ('relay', 'relay', 'lib', '/tests/relay-library'), + ('relay', 'snowbound-relay', 'bin', '/tests/relay-bin'), + ('other', 'other', 'lib', '/tests/other'), + ]: + artifacts.append(json.dumps({ + 'reason': 'compiler-artifact', 'profile': {'test': True}, + 'manifest_path': str(root / package / 'Cargo.toml'), + 'target': {'name': name, 'kind': [kind]}, 'executable': executable, + })) + + class RecordedRun: + folder = root + calls = [] + + def execute(self, command, log, deadline, **kwargs): + self.calls.append((command, log, kwargs.get('cwd'))) + if log.name == 'test.log': + log.write_text('\n'.join(artifacts)) + return 0 + with log.open('a') as stream: + stream.write(' '.join(command) + '\n') + return 1 if command == ['/tests/relay-library'] else 0 + + (root / 'test-seconds.json').write_text(json.dumps({ + 'relay.relay.test': 10, 'relay.snowbound-relay.bin': 20, 'doctests': 1, + })) + run = RecordedRun() + with patch.object(ci, 'RUNS', root): + status, logs, parts = ci.run_tests(run, 1, ['relay'], 1) + + by_name = {part['name']: part for part in parts} + self.assertEqual(status, 'failed') + self.assertEqual(set(by_name), { + 'relay.relay.test', 'relay.relay.lib', 'relay.snowbound-relay.bin', 'doctests', + }) + self.assertEqual(by_name['relay.relay.test']['status'], 'passed') + self.assertEqual(by_name['relay.relay.lib']['status'], 'failed') + self.assertEqual(len({part['log'] for part in parts}), 4) + self.assertEqual([path.name for path in logs], ['test.log', 'relay.relay.lib.log']) + self.assertEqual(Path(by_name['relay.relay.lib']['log']).read_text(), '/tests/relay-library\n') + self.assertEqual(Path(by_name['relay.relay.test']['log']).read_text(), '/tests/relay-integration\n') + self.assertEqual([call[0] for call in run.calls[1:4]], [ + ['/tests/relay-library'], ['/tests/relay-bin'], ['/tests/relay-integration'], + ]) + self.assertEqual([call[2] for call in run.calls[1:4]], [root / 'relay'] * 3) + self.assertFalse(any(call[0] == ['/tests/other'] for call in run.calls)) + timings = json.loads((root / 'test-seconds.json').read_text()) + self.assertIn('relay.relay.lib', timings) + self.assertIn('relay.relay.test', timings) + + +if __name__ == '__main__': + unittest.main() -- 2.54.0