77import os
88from pathlib import Path
99import re
10+ import shlex
1011import shutil
1112import subprocess
1213import sys
@@ -339,6 +340,39 @@ def test_tag_check_rejects_receipt_from_another_gate(self):
339340 manifest ['stages' ][0 ]['receipt' ]['gate' ] = 'parity'
340341 with self .assertRaises (ValueError ): self .gate .check_tag_ready (manifest )
341342
343+ def test_native_fixtures_do_not_require_a_host_timeout_installation (self ):
344+ from unittest .mock import patch
345+ import release_execution
346+ find_tool = shutil .which
347+
348+ def without_host_timeout (command , * args , ** kwargs ):
349+ if command in ('/usr/bin/timeout' , 'timeout' , 'gtimeout' ):
350+ return None
351+ return find_tool (command , * args , ** kwargs )
352+
353+ cases = ('test_cxx_gate_bounds_each_native_test_before_waiting_for_aggregate' ,
354+ 'test_package_receipt_survives_later_native_build_and_check' )
355+ for case in cases :
356+ with self .subTest (case = case ):
357+ fixture = type (self )(case )
358+ fixture .setUp ()
359+ self .addCleanup (fixture .doCleanups )
360+ with patch .object (release_execution .shutil , 'which' , side_effect = without_host_timeout ):
361+ getattr (fixture , case )()
362+
363+ def test_native_check_rejects_missing_deadline_before_make (self ):
364+ from types import SimpleNamespace
365+ from release_execution import ReleaseAttempt
366+ args = SimpleNamespace (output = self .directory , compiler = sys .executable , cc = sys .executable ,
367+ make = sys .executable , provider_prefix = self .directory , asan_options = 'detect_leaks=0' , jobs = 1 ,
368+ test_timeout_tool = str (self .directory / 'missing deadline' ))
369+ attempt = ReleaseAttempt (args , self .gate , {})
370+ logs = self .directory / 'logs' ; logs .mkdir ()
371+ commands = self .gate .Commands (logs )
372+ with self .assertRaisesRegex (ValueError , 'required per-test deadline tool unavailable' ):
373+ attempt .native_check (commands , self .directory , attempt .env , 10 )
374+ self .assertEqual (commands .records , [])
375+
342376 def test_cxx_gate_bounds_each_native_test_before_waiting_for_aggregate (self ):
343377 from types import SimpleNamespace
344378 from release_execution import ReleaseAttempt
@@ -348,16 +382,20 @@ def test_cxx_gate_bounds_each_native_test_before_waiting_for_aggregate(self):
348382 'Path("test/integ/native_case.log").write_text("actual failed case witness")\n '
349383 'Path("test/integ/native_case.trs").write_text(":test-result: FAIL\\ n")\n '
350384 'raise SystemExit(2)\n ' ); make .chmod (0o755 )
385+ # This fixture checks command arguments; its make never executes LOG_COMPILER.
386+ deadline = self .directory / 'selected deadline'
387+ deadline .write_text ('#!/bin/sh\n exit 99\n ' ); deadline .chmod (0o755 )
351388 args = SimpleNamespace (output = self .directory / 'attempt' , compiler = sys .executable , cc = sys .executable ,
352- make = str (make ), provider_prefix = self .directory , asan_options = 'detect_leaks=0' , jobs = 1 , test_timeout_tool = '/usr/bin/timeout' )
389+ make = str (make ), provider_prefix = self .directory , asan_options = 'detect_leaks=0' , jobs = 1 , test_timeout_tool = str ( deadline ) )
353390 attempt = ReleaseAttempt (args , self .gate , {'stages' : []}); attempt .source = ROOT
354391 lane = args .output / 'package/evidence/tls-no' ; build = lane / 'build'
355392 (build / 'src' ).mkdir (parents = True ); (build / 'config.h' ).write_text ('/* TLS off */' )
356393 (build / 'src/Makefile' ).write_text ('abs_top_srcdir = ' + str (ROOT ) + '\n ' )
357394 logs = self .directory / 'build-logs' ; logs .mkdir ()
358395 command = self .gate .Commands (logs )
359- with self .assertRaises (ValueError ): attempt .cxx (command , 'no' )
360- self .assertIn ('LOG_COMPILER=/usr/bin/timeout --kill-after=5s 180s' , command .records [0 ]['argv' ])
396+ with self .assertRaisesRegex (ValueError , 'command exited 2' ): attempt .cxx (command , 'no' )
397+ self .assertIn ('LOG_COMPILER=' + shlex .join ([str (deadline ), '--kill-after=5s' , '180s' ]),
398+ command .records [0 ]['argv' ])
361399 self .assertEqual ((logs / 'native-tests/integ/native_case.log' ).read_text (), 'actual failed case witness' )
362400 self .assertEqual ((logs / 'native-tests/integ/native_case.trs' ).read_text (), ':test-result: FAIL\n ' )
363401
@@ -740,10 +778,17 @@ def package(command):
740778 make .write_text ('#!' + sys .executable + '\n from pathlib import Path\n '
741779 'Path("test/.deps/case.Po").write_text("compiled dependencies")\n '
742780 'Path("test/test-suite.log").write_text("# TOTAL: 1\\ n# PASS: 1\\ n# SKIP: 0\\ n# XFAIL: 0\\ n# FAIL: 0\\ n# XPASS: 0\\ n# ERROR: 0\\ n")\n ' )
743- make .chmod (0o755 ); args .make = str (make ); args .test_timeout_tool = '/usr/bin/timeout'
781+ make .chmod (0o755 ); args .make = str (make )
782+ # Only availability and command construction are under test, not timeout timing.
783+ deadline = self .directory / 'selected deadline'
784+ deadline .write_text ('#!/bin/sh\n exit 99\n ' ); deadline .chmod (0o755 )
785+ args .test_timeout_tool = str (deadline )
744786 logs = output / 'later-native' ; logs .mkdir ()
745787 native , _ = attempt .normal_build ('no' )
746- attempt .native_check (self .gate .Commands (logs ), native , attempt .env , 10 )
788+ command = self .gate .Commands (logs )
789+ attempt .native_check (command , native , attempt .env , 10 )
790+ self .assertIn ('LOG_COMPILER=' + shlex .join ([str (deadline ), '--kill-after=5s' , '180s' ]),
791+ command .records [0 ]['argv' ])
747792 checks = self .gate .load_runner ('lib/release_checks.py' )
748793 self .assertEqual (checks .automake_result (native , 'Linux' )['tests' ], 1 )
749794 self .assertEqual ((build / 'test/.deps/case.Po' ).read_text (), 'compiled dependencies' )
0 commit comments