194 lines
7 KiB
Go
194 lines
7 KiB
Go
package taskloop
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParityEmbeddedManifestIsCompleteAndCurrent(t *testing.T) {
|
|
manifest, err := LoadEmbeddedParityManifest()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ValidateParityManifest(repoRoot(t), manifest); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestParityManifestRejectsMissingDuplicateUnclassifiedAndStaleEvidence(t *testing.T) {
|
|
root := parityFixtureRoot(t)
|
|
manifest := validParityFixture(t, root)
|
|
cases := []struct {
|
|
name string
|
|
mutate func(*ParityManifest)
|
|
want string
|
|
}{
|
|
{"missing disposal", func(m *ParityManifest) { m.Disposal.ReferenceFiles = m.Disposal.ReferenceFiles[:3] }, "disposal inventory is missing classified reference"},
|
|
{"duplicate behavior", func(m *ParityManifest) { m.Behaviors = append(m.Behaviors, m.Behaviors[0]) }, "duplicate behavior"},
|
|
{"unclassified", func(m *ParityManifest) { m.Behaviors[0].Disposition = "" }, "unclassified disposition"},
|
|
{"stale source", func(m *ParityManifest) { m.Behaviors[0].GoSource[0] = "missing.go" }, "stale path"},
|
|
{"python caller", func(m *ParityManifest) { m.Behaviors[0].ProductionCaller = "python" }, "lacks Go ownership evidence"},
|
|
{"hash drift", func(m *ParityManifest) { m.Disposal.ReferenceFiles[0].SHA256 = strings.Repeat("0", 64) }, "checksum drift"},
|
|
{"unknown disposal state", func(m *ParityManifest) { m.Disposal.State = "transitioning" }, "unknown disposal state"},
|
|
}
|
|
for _, tt := range cases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
copy := manifest
|
|
copy.Behaviors = append([]ParityBehavior(nil), manifest.Behaviors...)
|
|
copy.Disposal.ReferenceFiles = append([]ParityReferenceFile(nil), manifest.Disposal.ReferenceFiles...)
|
|
for i := range copy.Behaviors {
|
|
copy.Behaviors[i].GoSource = append([]string(nil), copy.Behaviors[i].GoSource...)
|
|
}
|
|
tt.mutate(©)
|
|
if err := ValidateParityManifest(root, copy); err == nil || !strings.Contains(err.Error(), tt.want) {
|
|
t.Fatalf("ValidateParityManifest error = %v, want %q", err, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDispositionReportIsStable(t *testing.T) {
|
|
manifest, err := LoadEmbeddedParityManifest()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
report := FormatParityReport(manifest)
|
|
for _, want := range []string{"parity: ok", "behaviors: 13", "disposal_state: retained", "disposal_files: 9", "milestone-completion-transition"} {
|
|
if !strings.Contains(report, want) {
|
|
t.Fatalf("report %q does not contain %q", report, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func parityFixtureRoot(t *testing.T) string {
|
|
t.Helper()
|
|
root := t.TempDir()
|
|
for _, path := range append(append([]string{}, parityFixtureReferenceFiles...), "owner.go") {
|
|
fullPath := filepath.Join(root, filepath.FromSlash(path))
|
|
if err := os.MkdirAll(filepath.Dir(fullPath), 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(fullPath, []byte(path), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return root
|
|
}
|
|
|
|
func validParityFixture(t *testing.T, root string) ParityManifest {
|
|
t.Helper()
|
|
manifest := ParityManifest{SchemaVersion: paritySchemaVersion, ReferenceRoot: "references", DeletionGate: "completion"}
|
|
for index, path := range parityFixtureReferenceFiles {
|
|
contents, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(path)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
manifest.Behaviors = append(manifest.Behaviors, ParityBehavior{
|
|
ID: "behavior-" + string(rune('a'+index)),
|
|
ReferenceSources: []string{path}, Disposition: "replace", GoOwner: "Go", GoSource: []string{"owner.go"}, GoTest: []string{"TestOwner"}, Invariants: "bounded", ProductionCaller: "go-only",
|
|
})
|
|
manifest.Disposal.ReferenceFiles = append(manifest.Disposal.ReferenceFiles, ParityReferenceFile{
|
|
Path: path, SHA256: sha256Hex(contents), Disposition: "replace", CoveredBy: []string{"behavior-" + string(rune('a'+index))},
|
|
})
|
|
}
|
|
manifest.Disposal.State = "retained"
|
|
manifest.Disposal.Procedure = "retain"
|
|
return manifest
|
|
}
|
|
|
|
var parityFixtureReferenceFiles = []string{
|
|
"references/dispatch.py",
|
|
"references/observer.py",
|
|
"references/tests/test_dispatch.py",
|
|
"references/tests/test_dispatch.py.orig",
|
|
}
|
|
|
|
func TestParityManifestRejectsUnrecordedRetainedFixture(t *testing.T) {
|
|
root := parityFixtureRoot(t)
|
|
manifest := validParityFixture(t, root)
|
|
extra := filepath.Join(root, "references", "tests", "extra.py")
|
|
if err := os.WriteFile(extra, []byte("fixture\n"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ValidateParityManifest(root, manifest); err == nil || !strings.Contains(err.Error(), "unrecorded retained fixture") {
|
|
t.Fatalf("ValidateParityManifest error = %v, want unrecorded retained fixture", err)
|
|
}
|
|
}
|
|
|
|
func TestParityManifestDisposalLifecycle(t *testing.T) {
|
|
root := parityFixtureRoot(t)
|
|
manifest := validParityFixture(t, root)
|
|
if err := ValidateParityManifest(root, manifest); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, reference := range manifest.Disposal.ReferenceFiles {
|
|
if err := os.Remove(filepath.Join(root, filepath.FromSlash(reference.Path))); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
manifest.Disposal.State = "disposed"
|
|
if err := ValidateParityManifest(root, manifest); err != nil {
|
|
t.Fatalf("disposed manifest: %v", err)
|
|
}
|
|
|
|
root = parityFixtureRoot(t)
|
|
manifest = validParityFixture(t, root)
|
|
manifest.Disposal.State = "disposed"
|
|
if err := ValidateParityManifest(root, manifest); err == nil || !strings.Contains(err.Error(), "retained fixture") {
|
|
t.Fatalf("disposed manifest with survivors error = %v", err)
|
|
}
|
|
|
|
root = parityFixtureRoot(t)
|
|
manifest = validParityFixture(t, root)
|
|
if err := os.Remove(filepath.Join(root, filepath.FromSlash(manifest.Disposal.ReferenceFiles[0].Path))); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ValidateParityManifest(root, manifest); err == nil || !strings.Contains(err.Error(), "missing fixture") {
|
|
t.Fatalf("retained manifest with missing fixture error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParityManifestDisposedRejectsMissingHistoricalInventory(t *testing.T) {
|
|
root := parityFixtureRoot(t)
|
|
manifest := validParityFixture(t, root)
|
|
for _, reference := range manifest.Disposal.ReferenceFiles {
|
|
if err := os.Remove(filepath.Join(root, filepath.FromSlash(reference.Path))); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
manifest.Disposal.State = "disposed"
|
|
if err := ValidateParityManifest(root, manifest); err != nil {
|
|
t.Fatalf("complete disposed manifest: %v", err)
|
|
}
|
|
manifest.Disposal.ReferenceFiles = manifest.Disposal.ReferenceFiles[:len(manifest.Disposal.ReferenceFiles)-1]
|
|
if err := ValidateParityManifest(root, manifest); err == nil || !strings.Contains(err.Error(), "disposal inventory is missing classified reference") {
|
|
t.Fatalf("disposed manifest with truncated history error = %v", err)
|
|
}
|
|
}
|
|
|
|
func sha256Hex(contents []byte) string {
|
|
digest := sha256.Sum256(contents)
|
|
return hex.EncodeToString(digest[:])
|
|
}
|
|
|
|
func repoRoot(t *testing.T) string {
|
|
t.Helper()
|
|
directory, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for {
|
|
if _, err := os.Stat(filepath.Join(directory, "go.mod")); err == nil {
|
|
return directory
|
|
}
|
|
parent := filepath.Dir(directory)
|
|
if parent == directory {
|
|
t.Fatal("repository root not found")
|
|
}
|
|
directory = parent
|
|
}
|
|
}
|