mirror of
https://github.com/storytold/craft-common-actions.git
synced 2026-11-15 15:02:24 +00:00
* release.yml: a publish input publishes a complete draft at the end of the run Part E of the faster-release plan: no one waits by the run to publish. Only on the release branch, not in a dry run, only when no build job failed (the job already requires that) or was cancelled, and only when the draft has at least as many assets as the last published release, so a skipped build job can't publish an incomplete release. Off by default; apps opt in. * publish: compare with the most recently published release, and say why in the summary gh release list orders by creation, so the newest-created release was the baseline; an older draft published later would have been missed. Every refusal now also writes its reason to the job summary, not only an error annotation. * tests: publish doesn't change the FreeBSD build, so it stays out of the cache key
371 lines
23 KiB
Python
371 lines
23 KiB
Python
"""Execute the cache action's Bash using a fixture guest and real local rsync.
|
|
|
|
SSH and remote rsync paths are mapped to temporary directories. This checks the
|
|
actual transfer scripts without needing a FreeBSD host or the Actions service.
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
import yaml
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
ACTION = yaml.load((ROOT / "actions/freebsd-cargo-cache/action.yml").read_text(), Loader=yaml.BaseLoader)
|
|
IDENTITY = next(step["run"] for step in ACTION["runs"]["steps"] if step.get("id") == "identity")
|
|
TRANSFER = next(step["run"] for step in ACTION["runs"]["steps"] if step.get("name") == "Transfer Cargo data across the VM boundary")
|
|
|
|
|
|
@unittest.skipUnless(shutil.which("rsync") and shutil.which("sha256sum"), "rsync and sha256sum required")
|
|
class CargoCacheTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory(prefix="freebsd-cache-test-")
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.root = Path(self.temp.name).resolve()
|
|
self.guest = self.root / "guest"
|
|
self.cache = self.root / "home/.cache/craft-freebsd-cargo"
|
|
self.tools = self.root / "tools"
|
|
self.tools.mkdir()
|
|
(self.root / "home").mkdir()
|
|
self.env = dict(os.environ, PATH=str(self.tools) + os.pathsep + os.environ["PATH"],
|
|
HOME=str(self.root / "home"), RUNNER_TEMP=str(self.root / "host"),
|
|
FIXTURE_GUEST=str(self.guest), REAL_RSYNC=shutil.which("rsync"),
|
|
CACHE_PHASE="restore", CACHE_PROFILE="debug", CACHE_TARGET="", CACHE_CAN_SAVE="true",
|
|
CACHE_CONFIGURATION='{"features":"heif","fonts":"abc"}',
|
|
CACHE_SOURCE_HASH="lock-and-build-config", GITHUB_SHA="commit1",
|
|
GITHUB_WORKSPACE=str(self.root),
|
|
GITHUB_OUTPUT=str(self.root / "outputs"), FIXTURE_COMPILER="rustc 1.99.1",
|
|
FIXTURE_ARCH="amd64", FIXTURE_PACKAGES="gtk3-1\nrust-1\n",
|
|
FIXTURE_FREEBSD="15.1-RELEASE")
|
|
self.tool("ssh", """import os, subprocess, sys
|
|
assert os.getcwd() == os.path.dirname(os.environ['FIXTURE_GUEST'])
|
|
command = sys.argv[2].replace('/var/tmp/', os.environ['FIXTURE_GUEST'] + '/var/tmp/')
|
|
login = os.environ.get('FIXTURE_LOGIN_SHELL', '/bin/sh')
|
|
flags = ['-f'] if login.endswith('csh') else []
|
|
script = sys.stdin.read().replace('/var/tmp/', os.environ['FIXTURE_GUEST'] + '/var/tmp/') if command == 'sh -eu' else None
|
|
sys.exit(subprocess.run([login, *flags, '-c', command], input=script, text=True).returncode)
|
|
""")
|
|
self.tool("rsync", """import os, subprocess, sys
|
|
with open(os.path.join(os.environ["HOME"], "rsync.log"), "a") as log:
|
|
log.write(" ".join(sys.argv[1:]) + "\\n")
|
|
args = []
|
|
items = iter(sys.argv[1:])
|
|
for arg in items:
|
|
if arg == '-e':
|
|
next(items)
|
|
elif arg.startswith('--rsync-path='):
|
|
continue
|
|
elif arg.startswith('freebsd:'):
|
|
args.append(os.environ['FIXTURE_GUEST'] + arg[len('freebsd:'):])
|
|
else:
|
|
args.append(arg)
|
|
sys.exit(subprocess.run([os.environ['REAL_RSYNC'], *args]).returncode)
|
|
""")
|
|
for name, variable in (("rustc", "FIXTURE_COMPILER"), ("uname", "FIXTURE_ARCH"),
|
|
("pkg", "FIXTURE_PACKAGES"), ("freebsd-version", "FIXTURE_FREEBSD")):
|
|
check_cwd = "assert os.getcwd() == os.environ['GITHUB_WORKSPACE']\n" if name == "rustc" else ""
|
|
self.tool(name, f"import os\n{check_cwd}print(os.environ[{variable!r}])\n")
|
|
|
|
def tool(self, name, script):
|
|
path = self.tools / name
|
|
path.write_text(f"#!{sys.executable}\n" + script)
|
|
path.chmod(0o755)
|
|
|
|
def run_script(self, script, **env):
|
|
return subprocess.run(["bash", "-c", script], cwd=self.root, env=dict(self.env, **env),
|
|
text=True, capture_output=True, timeout=20)
|
|
|
|
def transfer(self, phase, **env):
|
|
result = self.run_script(TRANSFER, CACHE_PHASE=phase, **env)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
|
|
def write(self, root, path, value):
|
|
file = root / path
|
|
file.parent.mkdir(parents=True, exist_ok=True)
|
|
file.write_text(value)
|
|
|
|
def identity(self, **env):
|
|
(self.root / "outputs").write_text("")
|
|
result = self.run_script(IDENTITY, **env)
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
return dict(line.split("=", 1) for line in (self.root / "outputs").read_text().splitlines())
|
|
|
|
def test_key_separates_toolchain_arch_native_packages_features_fonts_lock_and_profile(self):
|
|
first = self.identity()
|
|
self.assertEqual(first, self.identity())
|
|
for env in ({"FIXTURE_COMPILER": "rustc 1.98.1"}, {"FIXTURE_ARCH": "aarch64"},
|
|
{"FIXTURE_PACKAGES": "gtk3-2\nrust-1\n"}, {"FIXTURE_FREEBSD": "15.0-RELEASE"},
|
|
{"CACHE_CONFIGURATION": '{"features":"","fonts":"abc"}'},
|
|
{"CACHE_CONFIGURATION": '{"features":"heif","fonts":"def"}'},
|
|
{"CACHE_SOURCE_HASH": "different-lock-or-build-script"}, {"CACHE_PROFILE": "release"}):
|
|
with self.subTest(env=env):
|
|
self.assertNotEqual(first["prefix"], self.identity(**env)["prefix"])
|
|
updated = self.identity(GITHUB_SHA="commit2")
|
|
self.assertEqual(first["prefix"], updated["prefix"])
|
|
self.assertNotEqual(first["key"], updated["key"])
|
|
|
|
def test_only_the_source_hash_pins_an_exact_revision(self):
|
|
first = self.identity()
|
|
self.assertTrue(first["key"].startswith(first["prefix"]))
|
|
self.assertTrue(first["prefix"].startswith(first["fallback-prefix"]))
|
|
# A revision whose manifests moved (every release bumps a version) still
|
|
# falls back to the previous revision's compiled dependencies.
|
|
changed = self.identity(CACHE_SOURCE_HASH="different-lock-or-build-script")
|
|
self.assertEqual(first["fallback-prefix"], changed["fallback-prefix"])
|
|
self.assertNotEqual(first["key"], changed["key"])
|
|
# Anything that changes artifact compatibility must not fall back.
|
|
for env in ({"FIXTURE_COMPILER": "rustc 1.98.1"}, {"FIXTURE_ARCH": "aarch64"},
|
|
{"FIXTURE_PACKAGES": "gtk3-2\nrust-1\n"}, {"FIXTURE_FREEBSD": "15.0-RELEASE"},
|
|
{"CACHE_CONFIGURATION": '{"features":"heif","fonts":"def"}'},
|
|
{"CACHE_PROFILE": "release"}):
|
|
with self.subTest(env=env):
|
|
self.assertNotEqual(first["fallback-prefix"], self.identity(**env)["fallback-prefix"])
|
|
|
|
def test_cold_restore_is_valid_and_warm_restore_loads_only_selected_profile(self):
|
|
self.transfer("restore")
|
|
self.write(self.cache, "registry/src/dependency/lib.rs", "source")
|
|
self.write(self.cache, "git/db/repository/objects/pack/data", "git-object")
|
|
self.write(self.cache, "target/debug/deps/libdependency.rlib", "dev-artifact")
|
|
self.write(self.cache, "target/release/deps/libdependency.rlib", "release-artifact")
|
|
self.transfer("restore")
|
|
self.assertEqual((self.guest / "var/tmp/craft-cargo/registry/src/dependency/lib.rs").read_text(), "source")
|
|
self.assertEqual((self.guest / "var/tmp/craft-target/debug/deps/libdependency.rlib").read_text(), "dev-artifact")
|
|
self.assertFalse((self.guest / "var/tmp/craft-target/release/deps/libdependency.rlib").exists())
|
|
|
|
def test_package_checkpoint_and_save_keep_both_profiles_without_credentials(self):
|
|
self.transfer("restore")
|
|
self.write(self.guest, "var/tmp/craft-target/debug/deps/test-app", "test-binary")
|
|
self.write(self.guest, "var/tmp/craft-target/debug/incremental/state", "discard")
|
|
self.write(self.guest, "var/tmp/craft-cargo/credentials.toml", "do-not-cache")
|
|
self.write(self.guest, "var/tmp/craft-cargo/config.toml", "do-not-cache")
|
|
self.write(self.guest, "var/tmp/craft-cargo/registry/cache/dependency.crate", "crate")
|
|
self.write(self.cache, "target/release/deps/libdependency.rlib", "cached-release")
|
|
self.transfer("package")
|
|
self.assertFalse((self.guest / "var/tmp/craft-target/debug").exists())
|
|
self.assertEqual((self.cache / "target/debug/deps/test-app").read_text(), "test-binary")
|
|
self.assertFalse((self.cache / "target/debug/incremental").exists())
|
|
self.assertEqual((self.guest / "var/tmp/craft-target/release/deps/libdependency.rlib").read_text(), "cached-release")
|
|
self.write(self.guest, "var/tmp/craft-target/release/app", "new-release")
|
|
self.transfer("save")
|
|
self.assertEqual((self.cache / "target/debug/deps/test-app").read_text(), "test-binary")
|
|
self.assertEqual((self.cache / "target/release/app").read_text(), "new-release")
|
|
self.assertEqual((self.cache / "registry/cache/dependency.crate").read_text(), "crate")
|
|
self.assertFalse(any("credentials" in str(f) or f.name == "config.toml" for f in self.cache.rglob("*")))
|
|
|
|
def test_release_profile_round_trip_preserves_compiled_output(self):
|
|
self.write(self.cache, "target/release/deps/libdependency.rlib", "cached-release")
|
|
self.transfer("restore", CACHE_PROFILE="release")
|
|
self.assertEqual((self.guest / "var/tmp/craft-target/release/deps/libdependency.rlib").read_text(), "cached-release")
|
|
self.write(self.guest, "var/tmp/craft-target/release/app", "release")
|
|
self.transfer("save", CACHE_PROFILE="release")
|
|
self.assertEqual((self.cache / "target/release/app").read_text(), "release")
|
|
|
|
def test_pr_never_copies_guest_content_back_to_the_cache(self):
|
|
self.transfer("restore")
|
|
self.write(self.guest, "var/tmp/craft-target/debug/deps/untrusted", "PR-content")
|
|
self.transfer("package", CACHE_CAN_SAVE="false")
|
|
self.transfer("save", CACHE_CAN_SAVE="false")
|
|
self.assertFalse((self.cache / "target/debug/deps/untrusted").exists())
|
|
save = next(step for step in ACTION["runs"]["steps"] if step.get("uses", "").startswith("actions/cache/save@"))
|
|
self.assertIn("github.event_name != 'pull_request'", save["if"])
|
|
self.assertIn("github.event_name != 'pull_request_target'", save["if"])
|
|
|
|
def test_reported_large_profiles_are_saved_and_restored_without_pruning(self):
|
|
# Represent profiles >2 GiB without allocating gigabytes in a unit test.
|
|
# A local size-pruning implementation would discard these components.
|
|
self.tool("du", "print('3145728\\tfixture')\n")
|
|
self.transfer("restore")
|
|
for profile in ("debug", "release"):
|
|
self.write(self.guest, f"var/tmp/craft-target/{profile}/deps/app", profile * 16384)
|
|
self.write(self.guest, "var/tmp/craft-cargo/registry/src/dependency/lib.rs", "source")
|
|
self.transfer("save", CACHE_MAX_KIB="2097152")
|
|
for profile in ("debug", "release"):
|
|
self.assertEqual((self.cache / f"target/{profile}/deps/app").read_text(), profile * 16384)
|
|
self.assertEqual((self.cache / "registry/src/dependency/lib.rs").read_text(), "source")
|
|
# Restore both profiles in separate invocations, as the real VM does.
|
|
shutil.rmtree(self.guest / "var/tmp/craft-target")
|
|
self.transfer("restore", CACHE_MAX_KIB="2097152")
|
|
self.assertTrue((self.guest / "var/tmp/craft-target/debug/deps/app").is_file())
|
|
self.transfer("restore", CACHE_PROFILE="release", CACHE_MAX_KIB="2097152")
|
|
self.assertTrue((self.guest / "var/tmp/craft-target/release/deps/app").is_file())
|
|
self.assertTrue((self.cache / "target/debug/deps/app").is_file())
|
|
|
|
def test_failed_export_discards_partial_host_profile(self):
|
|
self.transfer("restore")
|
|
self.write(self.guest, "var/tmp/craft-target/debug/deps/app", "built")
|
|
self.tool("rsync", "import pathlib, sys\np = pathlib.Path(sys.argv[-1])\np.mkdir(parents=True, exist_ok=True)\n(p / 'partial').write_text('incomplete')\nsys.exit(23)\n")
|
|
result = self.run_script(TRANSFER, CACHE_PHASE="package")
|
|
self.assertEqual(result.returncode, 23, result)
|
|
self.assertFalse((self.cache / "target/debug").exists())
|
|
|
|
def test_save_without_registry_or_git_still_exports_compiled_profile(self):
|
|
self.write(self.guest, "var/tmp/craft-target/release/app", "built")
|
|
self.transfer("save", CACHE_PROFILE="release")
|
|
self.assertEqual((self.cache / "target/release/app").read_text(), "built")
|
|
|
|
@unittest.skipUnless(shutil.which("csh") or shutil.which("tcsh"), "csh/tcsh required")
|
|
def test_csh_login_shell_supports_identity_checkpoint_and_save(self):
|
|
self.env["FIXTURE_LOGIN_SHELL"] = shutil.which("csh") or shutil.which("tcsh")
|
|
self.assertTrue(self.identity()["key"])
|
|
self.test_package_checkpoint_and_save_keep_both_profiles_without_credentials()
|
|
|
|
def test_incremental_dependency_sources_survive_but_profile_state_is_excluded(self):
|
|
self.transfer("restore")
|
|
paths = ("registry/src/crate/incremental/lib.rs", "git/checkouts/repo/incremental/lib.rs")
|
|
for path in paths:
|
|
self.write(self.guest, "var/tmp/craft-cargo/" + path, "source")
|
|
self.write(self.guest, "var/tmp/craft-target/debug/incremental/state", "discard")
|
|
self.write(self.guest, "var/tmp/craft-target/debug/build/dependency/incremental/lib.rs", "keep")
|
|
self.transfer("save")
|
|
for path in paths:
|
|
self.assertEqual((self.cache / path).read_text(), "source")
|
|
self.assertFalse((self.cache / "target/debug/incremental").exists())
|
|
self.assertEqual((self.cache / "target/debug/build/dependency/incremental/lib.rs").read_text(), "keep")
|
|
|
|
def test_untouched_empty_guest_profile_preserves_host_artifacts(self):
|
|
self.write(self.cache, "target/release/app", "cached-release")
|
|
self.transfer("restore")
|
|
self.assertFalse(any((self.guest / "var/tmp/craft-target/release").iterdir()))
|
|
self.transfer("save")
|
|
self.assertEqual((self.cache / "target/release/app").read_text(), "cached-release")
|
|
|
|
def test_cross_target_round_trip_keeps_the_triple_directory(self):
|
|
triple = "aarch64-unknown-freebsd"
|
|
self.write(self.cache, "target/release/build/dependency/output", "host-build-script")
|
|
self.write(self.cache, f"target/{triple}/release/deps/libdependency.rlib", "cached-cross")
|
|
self.transfer("restore", CACHE_PROFILE="release", CACHE_TARGET=triple)
|
|
self.assertEqual((self.guest / "var/tmp/craft-target/release/build/dependency/output").read_text(), "host-build-script")
|
|
self.assertEqual((self.guest / f"var/tmp/craft-target/{triple}/release/deps/libdependency.rlib").read_text(), "cached-cross")
|
|
self.write(self.guest, f"var/tmp/craft-target/{triple}/release/app", "cross-release")
|
|
self.write(self.guest, f"var/tmp/craft-target/{triple}/release/incremental/state", "discard")
|
|
self.transfer("save", CACHE_PROFILE="release", CACHE_TARGET=triple)
|
|
self.assertEqual((self.cache / f"target/{triple}/release/app").read_text(), "cross-release")
|
|
self.assertEqual((self.cache / f"target/{triple}/release/deps/libdependency.rlib").read_text(), "cached-cross")
|
|
self.assertFalse((self.cache / f"target/{triple}/release/incremental").exists())
|
|
self.assertEqual((self.cache / "target/release/build/dependency/output").read_text(), "host-build-script")
|
|
|
|
def test_native_build_leaves_cross_directories_alone(self):
|
|
self.write(self.cache, "target/aarch64-unknown-freebsd/release/app", "other-arch")
|
|
self.transfer("restore", CACHE_PROFILE="release")
|
|
self.assertFalse((self.guest / "var/tmp/craft-target/aarch64-unknown-freebsd").exists())
|
|
|
|
def test_target_must_be_a_plain_triple(self):
|
|
for target in ("../../etc", "aarch64 unknown", "a;rm -rf /", "Aarch64/x"):
|
|
with self.subTest(target=target):
|
|
result = self.run_script(TRANSFER, CACHE_PROFILE="release", CACHE_TARGET=target)
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("invalid cache target", result.stderr)
|
|
|
|
def test_transport_failure_is_visible(self):
|
|
self.tool("rsync", "import sys\nsys.exit(23)\n")
|
|
result = self.run_script(TRANSFER)
|
|
self.assertEqual(result.returncode, 23, result)
|
|
|
|
|
|
class WorkflowCacheIntegrationTests(unittest.TestCase):
|
|
# Release inputs that do not change what the FreeBSD job compiles, so they stay out of the
|
|
# cache key. contributors-artifact names the run itself, and a key that held it could never
|
|
# be restored by a later run; dry-run must share the release's entry so a dry run warms it;
|
|
# publish only decides whether the finished draft is published.
|
|
RELEASE_INPUTS_OUTSIDE_THE_KEY = {
|
|
"app-title", "web-craft-fonts", "version", "contributors-artifact", "has-web", "has-riscv64",
|
|
"has-freebsd", "has-arch", "wix-version", "nfpm-version", "trunk-version",
|
|
"freebsd-architectures", "has-server", "dry-run", "publish",
|
|
}
|
|
|
|
def test_release_cache_key_names_every_input_that_changes_the_freebsd_build(self):
|
|
data = yaml.load((ROOT / ".github/workflows/release.yml").read_text(), Loader=yaml.BaseLoader)
|
|
inputs = set(data["on"]["workflow_call"]["inputs"])
|
|
steps = data["jobs"]["freebsd"]["steps"]
|
|
cache_steps = [step for step in steps if "/freebsd-cargo-cache@" in step.get("uses", "")]
|
|
self.assertEqual(len(cache_steps), 2)
|
|
for step in cache_steps:
|
|
configuration = step["with"]["configuration"]
|
|
self.assertNotIn("toJSON(inputs)", configuration)
|
|
self.assertIn("matrix.arch", configuration)
|
|
keyed = {name for name in inputs if f"inputs.{name})" in configuration}
|
|
# A new input must be put in the key or listed above as not affecting the build.
|
|
self.assertEqual(keyed | self.RELEASE_INPUTS_OUTSIDE_THE_KEY, inputs)
|
|
self.assertFalse(keyed & self.RELEASE_INPUTS_OUTSIDE_THE_KEY)
|
|
self.assertEqual(step["with"]["target"],
|
|
"${{ matrix.arch == 'aarch64' && 'aarch64-unknown-freebsd' || '' }}")
|
|
|
|
def test_restore_tries_the_exact_revision_then_a_compatible_one(self):
|
|
restore = next(step for step in ACTION["runs"]["steps"]
|
|
if step.get("uses", "").startswith("actions/cache/restore@"))
|
|
keys = [line.strip() for line in restore["with"]["restore-keys"].splitlines() if line.strip()]
|
|
self.assertEqual(keys, ["${{ steps.identity.outputs.prefix }}",
|
|
"${{ steps.identity.outputs.fallback-prefix }}"])
|
|
save = next(step for step in ACTION["runs"]["steps"]
|
|
if step.get("uses", "").startswith("actions/cache/save@"))
|
|
# Saving always writes the exact-revision entry the caller passed back in.
|
|
self.assertEqual(save["with"]["key"], "${{ inputs.key }}")
|
|
|
|
def test_host_cache_stays_outside_the_vm_shells_synced_work_tree(self):
|
|
# vmactions syncs ~/work, not just GITHUB_WORKSPACE; RUNNER_TEMP lives
|
|
# inside it, so placing a cache there would double-copy the artifacts.
|
|
for step in ACTION["runs"]["steps"]:
|
|
if step.get("uses", "").startswith("actions/cache/"):
|
|
self.assertEqual(step["with"]["path"], "~/.cache/craft-freebsd-cargo")
|
|
self.assertIn('cache="$HOME/.cache/craft-freebsd-cargo"', TRANSFER)
|
|
|
|
def test_rustup_installs_outside_the_forwarded_cargo_data_directory(self):
|
|
for filename in ("freebsd.yml", "release.yml"):
|
|
with self.subTest(workflow=filename):
|
|
data = yaml.load((ROOT / ".github/workflows" / filename).read_text(), Loader=yaml.BaseLoader)
|
|
vm = next(step["with"] for step in data["jobs"]["freebsd"]["steps"] if "prepare" in step.get("with", {}))
|
|
before_install = vm["prepare"].replace("${{ matrix.arch }}", "x86_64").split("pkg install", 1)[0]
|
|
result = subprocess.run(["sh", "-eu", "-c", before_install + '\ntest "${CARGO_HOME+x}" != x\ntest "${CARGO_TARGET_DIR+x}" != x'],
|
|
env=dict(os.environ, CARGO_HOME="/var/tmp/craft-cargo", CARGO_TARGET_DIR="/var/tmp/craft-target"),
|
|
text=True, capture_output=True, timeout=10)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
|
|
def test_both_workflows_restore_before_build_save_after_and_keep_release_coverage(self):
|
|
for filename in ("freebsd.yml", "release.yml"):
|
|
with self.subTest(workflow=filename):
|
|
data = yaml.load((ROOT / ".github/workflows" / filename).read_text(), Loader=yaml.BaseLoader)
|
|
job = data["jobs"]["freebsd"]
|
|
steps = job["steps"]
|
|
index = lambda name: next(i for i, step in enumerate(steps) if step.get("name") == name)
|
|
vm = next(step for step in steps if step.get("uses", "").startswith("step-security/freebsd-vm@"))
|
|
self.assertEqual(vm["with"]["copyback"], "false")
|
|
for variable in ("CARGO_HOME", "CARGO_TARGET_DIR", "CARGO_INCREMENTAL", "CARGO_BUILD_JOBS"):
|
|
self.assertIn(variable, vm["with"]["envs"].split())
|
|
cache_steps = [step for step in steps if "/freebsd-cargo-cache@" in step.get("uses", "")]
|
|
restore = next(step for step in cache_steps if step.get("id") == "cargo-cache")
|
|
save = next(step for step in cache_steps if step["with"].get("phase") == "save")
|
|
build = next(step for step in steps if step.get("shell") == "freebsd {0}" and ".cargo/env" in step["run"])
|
|
self.assertLess(steps.index(vm), steps.index(restore))
|
|
self.assertLess(steps.index(restore), steps.index(build))
|
|
self.assertLess(steps.index(build), steps.index(save))
|
|
self.assertNotIn("always()", save["if"])
|
|
for cache_step in cache_steps:
|
|
self.assertEqual(cache_step.get("continue-on-error"), "true")
|
|
self.assertEqual(cache_step.get("timeout-minutes"), "10")
|
|
self.assertIn("steps.cargo-cache.outputs.key != ''", save["if"])
|
|
self.assertIn("steps.cargo-cache.outcome", build["run"])
|
|
self.assertIn("pull_request", save["if"])
|
|
if filename == "freebsd.yml":
|
|
self.assertIn("toJSON(inputs)", restore["with"]["configuration"])
|
|
else:
|
|
self.assertEqual(restore["with"]["configuration"], save["with"]["configuration"])
|
|
self.assertEqual(restore["with"]["target"], save["with"]["target"])
|
|
if filename == "release.yml":
|
|
self.assertEqual(job["strategy"]["matrix"]["arch"], "${{ fromJSON(inputs.freebsd-architectures) }}")
|
|
self.assertEqual(restore["with"]["profile"], "release")
|
|
self.assertNotIn('rm -rf "dist/release/$name" /var/tmp/craft-target', build["run"])
|
|
self.assertLess(index("Build and package"), index("Copy release archives from the VM"))
|
|
upload = next(step for step in steps if step.get("uses", "").startswith("actions/upload-artifact@"))
|
|
self.assertLess(index("Copy release archives from the VM"), steps.index(upload))
|
|
self.assertLess(steps.index(upload), steps.index(save))
|
|
self.assertTrue(any(step.get("uses", "").startswith("actions/upload-artifact@") for step in steps))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|