cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

1-n-sigls
goq / scripts / tests / test_ci_cross_build_policy.py
test_ci_cross_build_policy.py 15.1K
1from __future__ import annotations23import unittest4from pathlib import Path56from scripts.verify_ci_cross_build_policy import PolicyError, verify, verify_gate_script789REPO_DIR = Path(__file__).resolve().parents[2]10WORKFLOW = (REPO_DIR / ".github" / "workflows" / "ci.yml").read_text(11 encoding="utf-8"12)13GATE_SCRIPT = (REPO_DIR / "scripts" / "verify-demo-build.sh").read_text(14 encoding="utf-8"15)161718def replace_once(source: str, old: str, new: str) -> str:19 if source.count(old) != 1:20 raise AssertionError(f"fixture source must contain exactly one {old!r}")21 return source.replace(old, new, 1)222324def remove_named_step(source: str, name: str, next_name: str) -> tuple[str, str]:25 marker = f" - name: {name}\n"26 next_marker = f" - name: {next_name}\n"27 start = source.index(marker)28 end = source.index(next_marker, start)29 return source[:start] + source[end:], source[start:end]303132def replace_once_in_demo_gate(source: str, old: str, new: str) -> str:33 prefix, marker, demo_gate = source.partition(" demo-gate:\n")34 if not marker:35 raise AssertionError("fixture source must contain the demo-gate job")36 return prefix + marker + replace_once(demo_gate, old, new)373839class CiCrossBuildPolicyTests(unittest.TestCase):40 def test_repository_workflow_passes(self) -> None:41 verify(WORKFLOW)42 verify_gate_script(GATE_SCRIPT)4344 def test_rejects_cross_build_helper_inside_false_branch(self) -> None:45 mutated = replace_once(46 GATE_SCRIPT,47 "./scripts/run-linux-cross-build-gate.sh",48 "if false; then\n./scripts/run-linux-cross-build-gate.sh\nfi",49 )50 with self.assertRaisesRegex(51 PolicyError, "executable prefix changed"52 ):53 verify_gate_script(mutated)5455 def test_rejects_successful_early_exit_before_cross_build_helper(self) -> None:56 mutated = replace_once(57 GATE_SCRIPT,58 "./scripts/run-linux-cross-build-gate.sh",59 "if true; then\n exit 0\nfi\n"60 "./scripts/run-linux-cross-build-gate.sh",61 )62 with self.assertRaisesRegex(PolicyError, "executable prefix changed"):63 verify_gate_script(mutated)6465 def test_rejects_failure_control_changes_before_cross_build_helper(self) -> None:66 prefixes = [67 "exit 0",68 "return 0",69 "trap 'exit 0' EXIT",70 "exec true",71 "set +e",72 ]73 for prefix in prefixes:74 with self.subTest(prefix=prefix):75 mutated = replace_once(76 GATE_SCRIPT,77 "./scripts/run-linux-cross-build-gate.sh",78 f"{prefix}\n./scripts/run-linux-cross-build-gate.sh",79 )80 with self.assertRaisesRegex(81 PolicyError, "executable prefix changed"82 ):83 verify_gate_script(mutated)8485 def test_rejects_failure_masked_cross_build_helper(self) -> None:86 mutated = replace_once(87 GATE_SCRIPT,88 "./scripts/run-linux-cross-build-gate.sh",89 "./scripts/run-linux-cross-build-gate.sh || true",90 )91 with self.assertRaisesRegex(PolicyError, "executable prefix changed"):92 verify_gate_script(mutated)9394 def test_rejects_demo_gate_run_bypass_even_with_decoy_comment(self) -> None:95 mutated = replace_once(96 WORKFLOW,97 " run: ./scripts/verify-demo-build.sh",98 " run: echo bypassed",99 )100 mutated += "\n# run: ./scripts/verify-demo-build.sh\n"101 with self.assertRaisesRegex(102 PolicyError, "does not execute the repository gate"103 ):104 verify(mutated)105106 def test_rejects_conditionally_disabled_cross_install(self) -> None:107 mutated = replace_once(108 WORKFLOW,109 " - name: Install pinned cross-build tools\n run: |",110 " - name: Install pinned cross-build tools\n"111 " if: false\n"112 " run: |",113 )114 with self.assertRaisesRegex(PolicyError, "unexpected or missing fields"):115 verify(mutated)116117 def test_rejects_checkout_ref_override(self) -> None:118 mutated = replace_once_in_demo_gate(119 WORKFLOW,120 " uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1",121 " uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1\n"122 " with:\n"123 " ref: main",124 )125 with self.assertRaisesRegex(126 PolicyError, "checkout step has unexpected or missing fields"127 ):128 verify(mutated)129130 def test_rejects_checkout_action_change(self) -> None:131 mutated = replace_once_in_demo_gate(132 WORKFLOW,133 "actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1",134 "actions/checkout@main",135 )136 with self.assertRaisesRegex(PolicyError, "checkout action is not digest-pinned"):137 verify(mutated)138139 def test_rejects_step_poisoning_cargo_environment(self) -> None:140 mutated = replace_once(141 WORKFLOW,142 " - name: Install Linux build dependencies\n",143 " - name: Poison Cargo environment\n"144 " run: printf '%s\\n' 'export RUSTC_WRAPPER=true' >> ~/.cargo/env\n\n"145 " - name: Install Linux build dependencies\n",146 )147 with self.assertRaisesRegex(148 PolicyError, "step list or order changed from the CI policy contract"149 ):150 verify(mutated)151152 def test_rejects_step_replacing_cargo_zigbuild(self) -> None:153 mutated = replace_once(154 WORKFLOW,155 " - name: Run complete demo gate\n",156 " - name: Replace cargo-zigbuild\n"157 " run: |\n"158 " printf '%s\\n' '#!/usr/bin/env bash' 'exit 0' "159 "> ~/.cargo/bin/cargo-zigbuild\n"160 " chmod 0755 ~/.cargo/bin/cargo-zigbuild\n\n"161 " - name: Run complete demo gate\n",162 )163 with self.assertRaisesRegex(164 PolicyError, "step list or order changed from the CI policy contract"165 ):166 verify(mutated)167168 def test_rejects_extra_steps_at_every_boundary(self) -> None:169 markers = [170 " - name: Check out repository\n",171 " - name: Install Linux build dependencies\n",172 " - name: Restore pinned cargo-zigbuild\n",173 " - name: Install pinned cross-build tools\n",174 " - name: Run complete demo gate\n",175 ]176 for position, marker in enumerate(markers):177 with self.subTest(position=position):178 mutated = replace_once_in_demo_gate(179 WORKFLOW,180 marker,181 f" - name: Extra step {position}\n"182 " run: echo unexpected\n\n"183 f"{marker}",184 )185 with self.assertRaisesRegex(186 PolicyError, "step list or order changed from the CI policy contract"187 ):188 verify(mutated)189190 mutated = WORKFLOW + (191 "\n"192 " - name: Extra trailing step\n"193 " run: echo unexpected\n"194 )195 with self.assertRaisesRegex(196 PolicyError, "step list or order changed from the CI policy contract"197 ):198 verify(mutated)199200 def test_rejects_required_step_reordering(self) -> None:201 cache_marker = " - name: Restore pinned cargo-zigbuild\n"202 install_marker = " - name: Install pinned cross-build tools\n"203 gate_marker = " - name: Run complete demo gate\n"204 cache_start = WORKFLOW.index(cache_marker)205 install_start = WORKFLOW.index(install_marker, cache_start)206 gate_start = WORKFLOW.index(gate_marker, install_start)207 cache_step = WORKFLOW[cache_start:install_start]208 install_step = WORKFLOW[install_start:gate_start]209 mutated = (210 WORKFLOW[:cache_start]211 + install_step212 + cache_step213 + WORKFLOW[gate_start:]214 )215 with self.assertRaisesRegex(216 PolicyError, "step list or order changed from the CI policy contract"217 ):218 verify(mutated)219220 def test_rejects_partial_linux_dependency_body_mutations(self) -> None:221 mutations = [222 (223 " ffmpeg \\\n",224 "",225 ),226 (227 " shellcheck\n",228 " shellcheck \\\n"229 " unzip\n",230 ),231 (232 " sudo apt-get update\n",233 " sudo apt-get update --quiet\n",234 ),235 ]236 for old, new in mutations:237 with self.subTest(replacement=new):238 mutated = replace_once(WORKFLOW, old, new)239 with self.assertRaisesRegex(240 PolicyError, "Linux dependency step body changed"241 ):242 verify(mutated)243244 def test_rejects_linux_dependency_step_field_changes(self) -> None:245 mutated = replace_once(246 WORKFLOW,247 " - name: Install Linux build dependencies\n run: |",248 " - name: Install Linux build dependencies\n"249 " env:\n"250 " BASH_ENV: ~/.cargo/env\n"251 " run: |",252 )253 with self.assertRaisesRegex(254 PolicyError, "Linux dependency step has unexpected or missing fields"255 ):256 verify(mutated)257258 def test_rejects_conditionally_disabled_demo_job(self) -> None:259 mutated = replace_once(260 WORKFLOW,261 " demo-gate:\n name: Complete demo gate",262 " demo-gate:\n if: false\n name: Complete demo gate",263 )264 with self.assertRaisesRegex(PolicyError, "must not be conditionally disabled"):265 verify(mutated)266267 def test_rejects_quoted_conditionally_disabled_demo_job(self) -> None:268 mutated = replace_once(269 WORKFLOW,270 " demo-gate:\n name: Complete demo gate",271 ' demo-gate:\n "if": false\n name: Complete demo gate',272 )273 with self.assertRaisesRegex(PolicyError, "must not be conditionally disabled"):274 verify(mutated)275276 def test_rejects_demo_job_continue_on_error(self) -> None:277 mutated = replace_once(278 WORKFLOW,279 " demo-gate:\n name: Complete demo gate",280 " demo-gate:\n continue-on-error: true\n"281 " name: Complete demo gate",282 )283 with self.assertRaisesRegex(PolicyError, "must not suppress failures"):284 verify(mutated)285286 def test_rejects_missing_pull_request_trigger(self) -> None:287 mutated = replace_once(WORKFLOW, " pull_request:\n", "")288 with self.assertRaisesRegex(289 PolicyError, "empty pull_request trigger"290 ):291 verify(mutated)292293 def test_rejects_filtered_pull_request_trigger(self) -> None:294 filters = [295 " branches: [main]",296 " paths: ['crates/**']",297 " types: [opened]",298 ]299 for filter_line in filters:300 with self.subTest(filter_line=filter_line):301 mutated = replace_once(302 WORKFLOW,303 " pull_request:\n",304 f" pull_request:\n{filter_line}\n",305 )306 with self.assertRaisesRegex(307 PolicyError, "exactly empty and unfiltered"308 ):309 verify(mutated)310311 def test_rejects_inline_pull_request_mapping(self) -> None:312 mutated = replace_once(WORKFLOW, " pull_request:", " pull_request: {}")313 with self.assertRaisesRegex(PolicyError, "must be exactly empty"):314 verify(mutated)315316 def test_rejects_changed_demo_job_identity_and_execution_limits(self) -> None:317 changes = [318 (" name: Complete demo gate", " name: Optional demo gate", "required name"),319 (320 " demo-gate:\n name: Complete demo gate\n"321 " runs-on: ubuntu-24.04",322 " demo-gate:\n name: Complete demo gate\n"323 " runs-on: ubuntu-latest",324 "ubuntu-24.04",325 ),326 (" timeout-minutes: 45", " timeout-minutes: 5", "45 minutes"),327 ]328 for old, new, error in changes:329 with self.subTest(field=old):330 mutated = replace_once(WORKFLOW, old, new)331 with self.assertRaisesRegex(PolicyError, error):332 verify(mutated)333334 def test_rejects_changed_demo_job_environment(self) -> None:335 changes = [336 (' CARGO_TERM_COLOR: always', ' CARGO_TERM_COLOR: never'),337 (' RUST_BACKTRACE: "1"\n', ""),338 (339 ' RUST_BACKTRACE: "1"',340 ' RUST_BACKTRACE: "1"\n CI_FAILURES_OPTIONAL: "1"',341 ),342 ]343 for old, new in changes:344 with self.subTest(replacement=new):345 mutated = replace_once(WORKFLOW, old, new)346 with self.assertRaisesRegex(347 PolicyError, "env changed from the CI policy contract"348 ):349 verify(mutated)350351 def test_rejects_failure_suppressing_workflow_shell_default(self) -> None:352 mutated = replace_once(353 WORKFLOW,354 "name: CI\n\non:",355 "name: CI\n\ndefaults:\n"356 " run:\n"357 " shell: 'bash {0} || true'\n\n"358 "on:",359 )360 with self.assertRaisesRegex(PolicyError, "workflow-level defaults are forbidden"):361 verify(mutated)362363 def test_ignores_tokens_in_comments_and_later_jobs(self) -> None:364 mutated, removed = remove_named_step(365 WORKFLOW,366 "Install pinned cross-build tools",367 "Run complete demo gate",368 )369 decoy = (370 "\n# Install pinned cross-build tools\n"371 "# GOQ_REQUIRE_LINUX_CROSS_BUILD: \"1\"\n"372 " later-decoy:\n"373 " runs-on: ubuntu-24.04\n"374 " steps:\n"375 f"{removed}"376 )377 with self.assertRaisesRegex(PolicyError, "must exist exactly once"):378 verify(mutated + decoy)379380 def test_rejects_wrong_install_body_even_with_tokens_in_later_job(self) -> None:381 mutated = replace_once(382 WORKFLOW,383 " cargo install cargo-zigbuild --locked --version 0.23.0",384 " echo skipped cargo install",385 )386 mutated += (387 "\n later-decoy:\n"388 " runs-on: ubuntu-24.04\n"389 " steps:\n"390 " - name: Decoy\n"391 " run: cargo install cargo-zigbuild --locked --version 0.23.0\n"392 )393 with self.assertRaisesRegex(PolicyError, "install step body changed"):394 verify(mutated)395396397if __name__ == "__main__":398 unittest.main()