Files
Agent-System-Dev/tests/test_component_checks.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

142 lines
10 KiB
Python

"""Local named checks prepare fixed sources and retain edits with real Go/Git."""
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
SOURCE = Path(__file__).resolve().parents[1]
@unittest.skipUnless(shutil.which("go"), "Go required")
class ComponentCheckTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix="component-check-")
self.addCleanup(self.tmp.cleanup)
self.base = Path(self.tmp.name)
self.root = self.base / "dev"
self.root.mkdir()
self.env = os.environ.copy()
for key in list(self.env):
if key.startswith("GIT_CONFIG_") or key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GOWORK", "COMPOSITION_SOURCE_MAP", "CI_DEPENDENCY_MIRRORS"):
self.env.pop(key, None)
self.env.update(GIT_CONFIG_NOSYSTEM="1", GIT_CONFIG_GLOBAL="/dev/null", GIT_ALLOW_PROTOCOL="file",
GIT_AUTHOR_NAME="Checks", GIT_AUTHOR_EMAIL="check@example.invalid",
GIT_COMMITTER_NAME="Checks", GIT_COMMITTER_EMAIL="check@example.invalid",
GOTOOLCHAIN="local", GOPROXY="off", GOSUMDB="off")
self.call(self.root, "git", "init", "--initial-branch=main")
shutil.copytree(SOURCE / "scripts", self.root / "scripts")
shutil.copyfile(SOURCE / "Makefile", self.root / "Makefile")
(self.root / ".gitignore").write_text(".workspace/\ngo.work\ngo.work.sum\ntests/integration/multi-instance/.build/\n")
app = self.base / "backend"
app.mkdir()
self.call(app, "git", "init", "--initial-branch=main")
(app / "go.mod").write_text("module example.com/backend\n\ngo 1.23\n")
(app / "source.go").write_text("package backend\nfunc Value() string {return \"fixed\"}\n")
(app / "source_test.go").write_text('package backend\nimport "testing"\nfunc TestValue(t *testing.T) {if Value() != "fixed" {t.Fatal(Value())}}\n')
(app / "cmd/agent-system").mkdir(parents=True)
(app / "cmd/agent-system/main.go").write_text('package main\nimport "fmt"\nfunc main(){fmt.Print("fixed-binary")}\n')
self.call(app, "git", "add", ".")
self.call(app, "git", "commit", "-m", "Fixed Backend")
sha = self.call(app, "git", "rev-parse", "HEAD").stdout.strip()
manifest = {"schema": 1, "components": [{"id": "Agent-System-Backend", "repo": str(app), "path": ".workspace/Agent-System-Backend", "legacy_path": "modules/backend", "role": "application", "modules": [{"path": "example.com/backend", "directory": "."}], "target": "main"}]}
(self.root / "components.json").write_text(json.dumps(manifest))
(self.root / "components.lock.json").write_text(json.dumps({"schema": 1, "components": {"Agent-System-Backend": {"commit": sha}}}))
self.call(self.root, "git", "add", ".")
self.call(self.root, "git", "commit", "-m", "Fixed Dev")
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=90)
if check:
self.assertEqual(result.returncode, 0, result.stdout)
return result
def test_backend_check_prepares_fixed_source_and_replays_owner_edits(self):
result = self.call(self.root, "make", "test-backend")
self.assertIn("GOWORK=off", result.stdout)
child = self.root / ".workspace/Agent-System-Backend"
self.assertEqual((child / "source.go").read_text(), 'package backend\nfunc Value() string {return "fixed"}\n')
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "edit", "--name", "Agent-System-Backend", "--branch", "feature/owned")
(child / "source.go").write_text('package backend\nfunc Value() string {return "owner-edit"}\n')
result = self.call(self.root, "make", "test-backend", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("owner-edit", result.stdout)
self.assertEqual(self.call(child, "git", "branch", "--show-current").stdout.strip(), "feature/owned")
self.assertIn('"owner-edit"', (child / "source.go").read_text())
def test_pg_gate_requires_environment_and_forwards_it_to_real_go_tests(self):
result = self.call(self.root, "make", "test-backend-pg", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertFalse((self.root / ".workspace").exists())
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "edit", "--name", "Agent-System-Backend", "--branch", "feature/pg-gate")
child = self.root / ".workspace/Agent-System-Backend"
(child / "pg_gate_test.go").write_text('package backend\nimport("os";"testing")\nfunc TestGateEnv(t *testing.T){if os.Getenv("AGENT_PG_TEST_DSN")!="fixture-dsn"||os.Getenv("AGENT_REDIS_TEST_ADDR")!="fixture-redis"{t.Fatal("gate environment missing")}}\n')
self.call(self.root, "make", "test-backend-pg", env=self.env | {"AGENT_PG_TEST_DSN": "fixture-dsn", "AGENT_REDIS_TEST_ADDR": "fixture-redis"})
def test_multi_instance_binary_is_built_inside_dev_root_and_reached_by_suite(self):
suite = self.root / "tests/integration/multi-instance"
suite.mkdir(parents=True)
(suite / "go.mod").write_text("module example.com/multi\n\ngo 1.23\n")
(suite / "binary_test.go").write_text('package multi\nimport("os";"os/exec";"testing")\nfunc TestBinary(t *testing.T){b,e:=exec.Command(os.Getenv("AGENT_BIN")).Output();if e!=nil||string(b)!="fixed-binary"{t.Fatalf("%s %v",b,e)}}\n')
self.call(self.root, "make", "test-multi-instance", env=self.env | {"AGENT_PG_TEST_DSN": "fixture-dsn"})
self.assertTrue((suite / ".build/agent-system").is_file())
self.assertFalse((self.root / ".workspace/tests").exists())
def test_new_pin_gate_validates_fixed_sources_and_rejects_local_replaces(self):
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "prepare")
self.call(self.root, "bash", "scripts/check-pins.sh")
child = self.root / ".workspace/Agent-System-Backend"
(child / "go.mod").write_text((child / "go.mod").read_text() + "\nreplace example.com/dependency => ../local\n")
result = self.call(self.root, "bash", "scripts/check-pins.sh", check=False)
self.assertNotEqual(result.returncode, 0, result.stdout)
self.assertIn("local replace", result.stdout)
def test_registered_private_transport_reaches_real_go_process_without_global_change(self):
manifest = json.loads((self.root / "components.json").read_text())
manifest["transport"] = {"go_private": ["private.example.test"], "git_rewrites": [{"from": "https://private.example.test/", "to": self.base.as_uri() + "/"}]}
(self.root / "components.json").write_text(json.dumps(manifest))
global_config = self.base / "global.gitconfig"
global_config.write_text("[user]\n name = existing-user\n")
env = self.env | {"GIT_CONFIG_GLOBAL": str(global_config)}
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "edit", "--name", "Agent-System-Backend", "--branch", "feature/transport", env=env)
child = self.root / ".workspace/Agent-System-Backend"
(child / "transport_test.go").write_text('package backend\nimport("os";"testing")\nfunc TestTransport(t *testing.T){if os.Getenv("GOPRIVATE")!="private.example.test"||os.Getenv("GOWORK")!="off"||os.Getenv("GIT_CONFIG_COUNT")==""{t.Fatal("process-scoped transport missing")}}\n')
before = global_config.read_bytes()
self.call(self.root, "make", "test-backend", env=env)
self.assertEqual(global_config.read_bytes(), before)
def test_explicit_overlay_uses_registered_workspace_and_preserves_default_version_mode(self):
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "edit", "--name", "Agent-System-Backend", "--branch", "feature/overlay")
child = self.root / ".workspace/Agent-System-Backend"
work = self.root / "go.work"
(child / "overlay_test.go").write_text('package backend\nimport("os";"testing")\nfunc TestOverlay(t *testing.T){if os.Getenv("GOWORK")!="' + str(work) + '"{t.Fatal("registered overlay missing")}}\n')
self.call(self.root, "make", "test-backend", "LOCAL_OVERLAY=1")
default = self.call(self.root, "make", "test-backend", check=False)
self.assertNotEqual(default.returncode, 0, default.stdout)
self.assertIn("registered overlay missing", default.stdout)
self.assertTrue(work.is_file())
def test_make_pin_gate_uses_map_in_gitless_formal_root_and_ignores_external_rdm_identity(self):
self.call(self.root, "python3", "scripts/workspace.py", "--root", str(self.root), "prepare")
formal = self.base / "formal"
formal.mkdir()
shutil.copytree(self.root / "scripts", formal / "scripts")
for name in ("Makefile", "components.json", "components.lock.json"):
shutil.copy2(self.root / name, formal / name)
entry = json.loads((self.root / "components.json").read_text())["components"][0]
locked = json.loads((self.root / "components.lock.json").read_text())["components"][entry["id"]]["commit"]
mapping = {"schema": 1, "root_commit": self.call(self.root, "git", "rev-parse", "HEAD").stdout.strip(),
"components": {entry["id"]: {"path": str(self.root / entry["path"]), "repo": entry["repo"], "commit": locked},
"Agent-System-RDM": {"path": str(self.base / "external-rdm"), "repo": "external registered by composer", "commit": locked}}}
source_map = self.base / "map.json"
source_map.write_text(json.dumps(mapping))
self.call(formal, "make", "test-pins", env=self.env | {"COMPOSITION_SOURCE_MAP": str(source_map)})
self.assertFalse((formal / ".workspace").exists())
if __name__ == "__main__":
unittest.main()