Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -662,7 +662,7 @@ jobs:
# just replayed should ignore inode changes, so a reused runner does not
# carry it into an unrelated job.
- name: Forget the adopted-build inode override
if: always() && steps.seed-derived-data.outputs.hit == 'true'
if: always() && steps.seed-derived-data.outcome != 'skipped'
continue-on-error: true
run: defaults delete com.apple.dt.XCBuild IgnoreFileSystemDeviceInodeChanges || true

Expand Down
56 changes: 0 additions & 56 deletions .github/workflows/e2e-derived-data-seed.yml

This file was deleted.

196 changes: 96 additions & 100 deletions .github/workflows/test-e2e.yml

Large diffs are not rendered by default.

38 changes: 36 additions & 2 deletions tests/test_ci_e2e_compilation_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def setUp(self):
self.env = dict(os.environ, GITHUB_WORKSPACE=str(self.workspace),
RUNNER_TEMP=str(self.root), GITHUB_RUN_ID='11', GITHUB_RUN_ATTEMPT='1',
GITHUB_ENV=str(self.root / 'env'), GITHUB_OUTPUT=str(self.root / 'output'),
PATH=str(tools) + ':' + os.environ['PATH'], FIXTURE_XCODE='Xcode 26.6')
PATH=str(tools) + ':' + os.environ['PATH'], FIXTURE_XCODE='Xcode 26.6',
CMUX_CI_CANONICAL_ROOT=str(self.root / 'canonical'))

def run_step(self, name, job='build', **env):
return subprocess.run(['bash', '-eu', '-o', 'pipefail', '-c', step(name, job)['run']],
Expand Down Expand Up @@ -156,7 +157,7 @@ def test_one_build_serves_the_test_job_and_every_retry(self):
# and `test` consumes that exact artifact. A rerun of a failed `test`
# job re-downloads the product instead of recompiling it.
build = JOBS['build']
compiles = [s for s in build if 'compile-app-host-test-product.sh build' in (s.get('run') or '')]
compiles = [s for s in build if 'compile-app-host-test-product.sh canonical-build' in (s.get('run') or '')]
self.assertEqual(len(compiles), 1, 'build must compile exactly once')
for job in ('test',):
for entry in JOBS[job]:
Expand All @@ -175,6 +176,39 @@ def test_one_build_serves_the_test_job_and_every_retry(self):
self.assertEqual(outputs['sha256'], '${{ steps.package.outputs.sha256 }}')
self.assertEqual(WORKFLOW['jobs']['test']['needs'], ['resolve-ref', 'filter', 'runner', 'build'])

def test_the_build_adopts_the_admission_seed_at_the_canonical_root(self):
# The seed is only reusable at the paths it was built at, so the build
# must run at the canonical root under compile admission's DerivedData
# name, key the seed the way ci-macos.yml does, and adopt it for the
# tested revision before compiling.
names = [entry.get('name') for entry in JOBS['build']]
admission = (ROOT / '.github/workflows/ci-macos.yml').read_text()
self.assertIn('admission-derived-data-v1-${{ runner.os }}-${{ runner.arch }}-', admission)
key = step('Compute the DerivedData seed key')
self.assertIn('canonical-fingerprint "$CMUX_DERIVED_DATA_PATH"', key['run'])
self.assertIn('admission-derived-data-v1-${{ runner.os }}-${{ runner.arch }}-$fingerprint-', key['run'])
start = step('Start the DerivedData seed download')
self.assertIn('seed_derived_data.py start', start['run'])
self.assertIn('"$TEST_REF"', start['run'])
adopt = step('Adopt the DerivedData seed')
self.assertIn('seed_derived_data.py adopt', adopt['run'])
self.assertIn('"$CMUX_CI_CANONICAL_SRC" "$CMUX_DERIVED_DATA_PATH"', adopt['run'])
self.assertIn('"$TEST_REF"', adopt['run'])
self.assertIs(adopt.get('continue-on-error'), True)
resolve = step('Resolve Swift packages')
self.assertIn('compile-app-host-test-product.sh canonical-resolve', resolve['run'])
self.assertLess(names.index('Start the DerivedData seed download'), names.index('Resolve Swift packages'))
self.assertLess(names.index('Resolve Swift packages'), names.index('Adopt the DerivedData seed'))
self.assertLess(names.index('Adopt the DerivedData seed'), names.index('Build the app-host and UI test product'))
values = self.prepare()
self.assertEqual(values['CMUX_DERIVED_DATA_PATH'],
str(self.root / 'canonical' / 'derived-data-compile-admission'))
# The CAS path is a compiler argument: a different one than admission
# passes invalidates every compile the seed carries.
self.assertIn('CMUX_COMPILE_ADMISSION_CAS=${CMUX_CI_CANONICAL_ROOT:-/private/tmp/cmux-ci}/compile-admission-cas', admission)
self.assertEqual(values['CMUX_E2E_COMPILATION_CACHE'],
str(self.root / 'canonical' / 'compile-admission-cas'))

def test_the_test_job_verifies_the_product_before_using_it(self):
# A transport is allowed to miss; it is not allowed to hand over
# unverified bytes. The restore step checks the archive SHA-256 that
Expand Down
7 changes: 3 additions & 4 deletions tests/test_ci_self_hosted_guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,9 @@ allowed = {
("build", "compilation-cache-bound", "Bound E2E compilation cache", ""),
("build", "revision-on-main", "Check the selected revision against main", ""),
("build", "reuse", "Reuse a compiled product instead of building one", ""),
("build", "warm", "Adopt main's DerivedData", ""),
("build", "record-inputs", "Record build input times", ""),
("build", "warm-package", "Package DerivedData for later builds", ""),
("build", None, "Publish DerivedData for later builds", "actions/upload-artifact"),
("build", None, "Start the DerivedData seed download", ""),
("build", "seed", "Adopt the DerivedData seed", ""),
("build", None, "Forget the adopted-build inode override", ""),
("test", "parallel-product", "Read the compiled test product over parallel range requests", ""),
}
for job_id, job in document["jobs"].items():
Expand Down
24 changes: 0 additions & 24 deletions tests/test_e2e_warm_derived_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,30 +197,6 @@ def test_a_contained_archive_extracts(self):
self.assertTrue((destination / "Build/Intermediates.noindex/a.o").is_file())


class InterruptedAdoption(unittest.TestCase):
"""A timed-out adoption must never reach the build.

The script's own cleanup runs only when it raises. A step timeout kills
it first, so the workflow has to discard what it left behind.
"""

def test_an_unfinished_adoption_is_discarded_before_the_build(self):
import yaml

workflow = Path(__file__).resolve().parents[1] / ".github/workflows/test-e2e.yml"
steps = yaml.safe_load(workflow.read_text())["jobs"]["build"]["steps"]
names = [step.get("name") for step in steps]
warm = names.index("Adopt main's DerivedData")
build = names.index("Build the app-host and UI test product")
discard = [
index for index, step in enumerate(steps)
if warm < index < build
and "steps.warm.outcome != 'success'" in str(step.get("if", ""))
and 'rm -rf -- "$CMUX_DERIVED_DATA_PATH"' in str(step.get("run", ""))
]
self.assertEqual(len(discard), 1, "no step discards an adoption that did not finish")
self.assertNotIn("continue-on-error", steps[discard[0]])


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion tests/test_reuse_app_host_products.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ def test_e2e_identity_binds_the_helpers_its_build_job_runs(self):
workflow = (root / ".github/workflows/ci-macos.yml").read_text()
e2e_workflow = (root / ".github/workflows/test-e2e.yml").read_text()
source = f"100644 blob {'1' * 40}\tSources/App.swift"
helper = "scripts/ci/e2e_warm_derived_data.py"
helper = "scripts/ci/seed_derived_data.py"
base = identity.identity_from_tree_lines([source, f"100644 blob {'2' * 40}\t{helper}"], workflow, e2e_workflow)
edited = identity.identity_from_tree_lines([source, f"100644 blob {'3' * 40}\t{helper}"], workflow, e2e_workflow)

Expand Down
10 changes: 8 additions & 2 deletions tests/test_seed_derived_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,12 +443,18 @@ def test_only_the_nightly_seeder_writes_the_seed_and_admission_reads_the_same_ke
self.assertLess(adopt_at, compile_at)
self.assertLess(compile_at, forget_at)
self.assertEqual(adopt["env"]["SEED_PREFIX"], written[: -len(suffix)])
self.assertIn("steps.seed-derived-data.outputs.hit == 'true'", forget["if"])
# Adopt writes the override before it can time out, so clear it
# whenever adopt ran, not only when it reported a hit.
self.assertIn("steps.seed-derived-data.outcome != 'skipped'", forget["if"])

for path in (ROOT / ".github/workflows").glob("*.yml"):
text = path.read_text()
if "admission-derived-data-" in text and path.name not in {"nightly.yml", "ci-macos.yml", "seed-derived-data.yml"}:
if "admission-derived-data-" in text and path.name not in {"nightly.yml", "ci-macos.yml", "seed-derived-data.yml", "test-e2e.yml"}:
self.fail(f"{path.name} names the admission DerivedData seed")
# E2E builds adopt the same seed but only read it.
e2e = (ROOT / ".github/workflows/test-e2e.yml").read_text()
for command in re.findall(r"seed_derived_data\.py (\w+)", e2e):
self.assertIn(command, {"start", "adopt"})
self.assertNotIn("secrets.", json.dumps(adopt))

def test_every_main_push_seeds_incrementally_under_the_key_admission_reads(self):
Expand Down
Loading