Files
Agent-System-Dev/tests/test_svc_build_cutover.py
T
weichen 93ac63fb3d Compose immutable dependency sources and preserve branch transitions
Why:
Replace repeated submodule source pointers with consumer-owned language versions and fixed application sources, keeping independent repository edits and existing branches intact.

Evidence:
SELF task receipts contain startup/sync evidence, real Go and npm consumption, Native overlay/unlink, safe transition/ref preservation, credential boundary and historical build regressions. Exact committed full composition and RDM checks follow this checkpoint; formal acceptance and GATE CI remain pending.

Impact:
Add isolated build/test composition, source provenance and safe owner refresh, adapt governance and service build callers, remove replaced index links while retaining original Git metadata, and adopt reusable dependency standards.

Refs:
Documents approved dependency-workspace r1/R1-R15 and U1-U7 implementation plan; feature/dependency-workspace task card.
2026-10-13 17:18:07 +00:00

139 lines
8.9 KiB
Python

"""Source adapters exercise real Git/Go/npm builds without starting services."""
import json
import os
from pathlib import Path
import shutil
import subprocess
import unittest
import zipfile
import test_composition
SOURCE = Path(__file__).resolve().parents[1]
@unittest.skipUnless(all(shutil.which(command) for command in ("git", "go", "node", "npm")), "Git/Go/Node/npm required")
class SvcBuildCutoverTests(unittest.TestCase):
def setUp(self):
self.fixture = test_composition.CompositionTests(methodName="runTest")
self.fixture.setUp()
self.addCleanup(self.fixture.doCleanups)
self.base, self.root, self.env = self.fixture.base, self.fixture.root, self.fixture.env
self.env = self.env.copy()
for key in ("COMPOSITION_ROOT", "DEV_ROOT", "COMPOSITION_SOURCE_MAP"):
self.env.pop(key, None)
(self.root / "scripts/workspace").mkdir(parents=True)
shutil.copy2(SOURCE / "scripts/build-composition.py", self.root / "scripts/build-composition.py")
shutil.copy2(SOURCE / "scripts/workspace/transport.py", self.root / "scripts/workspace/transport.py")
(self.root / ".gitignore").write_text("bin/\nprivate/\n")
(self.root / "Makefile").write_text("build frontend:\n\tpython3 scripts/build-composition.py --root . --target $@\n")
self.sha = self.fixture.commit(self.root)
self.cache = self.base / "cache"
self.call(self.base, "git", "clone", str(self.root), str(self.cache))
self.runtime = self.base / "runtime"
self.runtime.mkdir()
wrappers = self.base / "tools"
wrappers.mkdir()
git = shutil.which("git")
(wrappers / "git").write_text('#!/usr/bin/env bash\nfor arg in "$@"; do if [ "$arg" = submodule ]; then echo "recursive submodule acquisition forbidden" >&2; exit 63; fi; done\nexec ' + git + ' "$@"\n')
(wrappers / "git").chmod(0o755)
self.env["PATH"] = str(wrappers) + os.pathsep + self.env["PATH"]
def call(self, cwd, *args, check=True, env=None):
result = subprocess.run(args, cwd=cwd, env=env or self.env, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=120)
if check:
self.assertEqual(result.returncode, 0, result.stdout)
return result
def adapter(self, commands, check=True, env=None):
code = 'source "$1"\nROOT="$2"; SRC_DIR="$3"; RUNTIME="$4"; ENV_NAME=fixture\n' + commands
return self.call(self.base, "bash", "-c", code, "cutover", str(SOURCE / "scripts/svc.sh"), str(self.root), str(self.cache), str(self.runtime), check=check, env=env)
def test_versioned_dev_materialization_uses_composer_without_submodules(self):
self.adapter('materialize_dev_commit "' + self.sha + '" dev build')
binary = self.runtime / ".worktrees/dev/bin/agent-system"
self.assertEqual(self.call(self.base, str(binary)).stdout, "immutable-page")
resolved = json.loads((binary.parent / "resolved-manifest.json").read_text())
self.assertEqual(resolved["root"]["commit"], self.sha)
def test_versioned_static_adapter_resolves_published_frontend_artifact(self):
self.adapter('wt=$(materialize_dev_commit "' + self.sha + '" frontend frontend)\nsvc_frontend_dist "$wt"')
dist = self.runtime / ".worktrees/frontend/bin/frontend-dist"
self.assertEqual((dist / "index.html").read_text(), "immutable-page")
def test_mismatched_existing_cache_is_rejected_before_candidate_build(self):
path = self.runtime / ".worktrees/reused"
self.call(self.base, "git", "-C", str(self.cache), "worktree", "add", "--detach", str(path), self.sha)
(self.root / "new-marker").write_text("new candidate\n")
new_sha = self.fixture.commit(self.root)
self.call(self.base, "git", "-C", str(self.cache), "fetch", "origin")
result = self.adapter('materialize_dev_commit "' + new_sha + '" reused build', check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertEqual(self.call(path, "git", "rev-parse", "HEAD").stdout.strip(), self.sha)
self.assertFalse((path / "bin").exists())
def test_cache_recycling_preserves_ignored_unique_files(self):
path = self.runtime / ".worktrees/unique"
self.call(self.base, "git", "-C", str(self.cache), "worktree", "add", "--detach", str(path), self.sha)
(path / "private").mkdir()
(path / "private/unique").write_text("owner data\n")
self.adapter('recycle_worktree unique')
self.assertTrue((path / "private/unique").is_file())
self.assertEqual((path / "private/unique").read_text(), "owner data\n")
def test_versioned_rdm_adapter_passes_fixed_dev_root_to_product_wrapper(self):
rdm = self.base / "rdm"
rdm.mkdir()
self.call(rdm, "git", "init", "-q", "-b", "main")
self.call(rdm, "git", "config", "user.email", "fixture@example.invalid")
self.call(rdm, "git", "config", "user.name", "Fixture")
version = "v0.0.0-" + self.call(self.fixture.backend, "git", "show", "-s", "--format=%cd", "--date=format-local:%Y%m%d%H%M%S", self.fixture.backend_sha).stdout.strip() + "-" + self.fixture.backend_sha[:12]
proxy = self.base / "proxy/example.com/backend/@v"
proxy.mkdir(parents=True)
(proxy / (version + ".mod")).write_text((self.fixture.backend / "go.mod").read_text())
(proxy / (version + ".info")).write_text(json.dumps({"Version": version, "Time": "2026-10-13T00:00:00Z"}))
(proxy / "list").write_text(version + "\n")
with zipfile.ZipFile(proxy / (version + ".zip"), "w") as archive:
for relative in self.call(self.fixture.backend, "git", "ls-files").stdout.splitlines():
archive.write(self.fixture.backend / relative, "example.com/backend@" + version + "/" + relative)
(rdm / "go.mod").write_text("module git-ssh.u1680832.nyat.app/weichen/Agent-System-RDM.git\n\ngo 1.23\nrequire example.com/backend " + version + "\n")
(rdm / "main.go").write_text('package main\nimport("fmt";"example.com/backend/web")\nfunc main(){fmt.Print(web.Page())}\n')
(rdm / "Makefile").write_text('build:\n\t@test -n "$(COMPOSITION_ROOT)"\n\tpython3 "$(COMPOSITION_ROOT)/scripts/build-composition.py" --root "$(COMPOSITION_ROOT)" --target build --rdm-source . --output bin\n\tcp bin/agent-system-rdm-product bin/agent-system-rdm\n')
env = self.env | {"GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(self.base / "module-cache")}
self.call(rdm, "go", "mod", "download", "all", env=env)
rdm_sha = self.fixture.commit(rdm)
self.adapter('RDM_SRC_DIR="' + str(rdm) + '"\nmaterialize_rdm_commit "' + rdm_sha + '" rdm', env=env)
binary = self.runtime / ".worktrees/rdm-rdm/bin/agent-system-rdm"
self.assertEqual(self.call(self.base, str(binary)).stdout, "immutable-page")
self.assertEqual(json.loads((binary.parent / "resolved-manifest.json").read_text())["root"]["commit"], self.sha)
def test_e2e_build_only_uses_versioned_sources_without_runtime_or_api_calls(self):
self.test_versioned_rdm_adapter_passes_fixed_dev_root_to_product_wrapper()
rdm = self.base / "rdm"
rdm_sha = self.call(rdm, "git", "rev-parse", "HEAD").stdout.strip()
self.call(rdm, "git", "update-ref", "refs/remotes/origin/main", rdm_sha)
shutil.copytree(SOURCE / "scripts/workspace", self.root / "scripts/workspace", dirs_exist_ok=True)
for name in ("workspace.py", "component-source.py", "svc.sh", "e2e.sh"):
shutil.copy2(SOURCE / "scripts" / name, self.root / "scripts" / name)
(self.root / ".gitignore").write_text("bin/\nprivate/\n.workspace/\ngo.work\n")
self.fixture.commit(self.root)
curl = self.base / "tools/curl"
curl.write_text('#!/usr/bin/env bash\necho "runtime API must not execute in build-only mode" >&2\nexit 71\n')
curl.chmod(0o755)
output = self.base / "e2e"
env = self.env | {"RDM_REPO": str(rdm), "E2E_OUT": str(output), "GOPROXY": (self.base / "proxy").as_uri(), "GOMODCACHE": str(self.base / "module-cache")}
unrelated = subprocess.Popen(["sleep", "60"])
self.addCleanup(lambda: (unrelated.terminate(), unrelated.wait()) if unrelated.poll() is None else None)
env.update(API_PID=str(unrelated.pid), UI_PID=str(unrelated.pid))
result = self.call(self.root, "bash", "scripts/e2e.sh", "--build-only", env=env)
self.assertIsNone(unrelated.poll(), "build-only must not kill inherited process identities")
self.assertIn("runtime/browser/model acceptance NOT_RUN", result.stdout)
self.assertEqual(self.call(self.base, str(output / "agent-system-rdm")).stdout, "immutable-page")
self.assertEqual((self.root / "bin/frontend-dist/index.html").read_text(), "immutable-page")
self.assertFalse((output / "data").exists())
self.assertFalse((output / "cookies.txt").exists())
if __name__ == "__main__":
unittest.main()