mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-09 18:08:31 -10:00
597 lines
18 KiB
Go
597 lines
18 KiB
Go
package agenteval
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestLoadSuiteParsesJSONAndNormalizesDeterministicFields(t *testing.T) {
|
|
path := writeSuite(t, `{
|
|
"id": "quality-context",
|
|
"name": "Quality context",
|
|
"description": "Offline regression tasks",
|
|
"tasks": [{
|
|
"id": "edit-reader",
|
|
"name": "Edit reader",
|
|
"description": "Keep prompt context tight.",
|
|
"tags": ["repo-map", "tool-use"],
|
|
"difficulty": "medium",
|
|
"prompt": "Update the reader.",
|
|
"workspaceFixture": "fixtures/reader",
|
|
"requiredTraceEvents": ["tool:read_file", "tool:apply_patch"],
|
|
"contextChecks": {
|
|
"requiredFiles": ["internal/reader/a/../b.go"],
|
|
"forbiddenFiles": ["node_modules/cache.txt"]
|
|
},
|
|
"verificationCommands": [
|
|
{"id": "test", "name": "Tests", "command": ["go", "test", "./..."]}
|
|
],
|
|
"expectedChangedFiles": ["internal/reader/a/../b.go", "internal/reader/a.go"],
|
|
"forbiddenChangedFiles": ["docs/generated.log"]
|
|
}]
|
|
}`)
|
|
|
|
suite, err := LoadSuite(path)
|
|
if err != nil {
|
|
t.Fatalf("LoadSuite returned error: %v", err)
|
|
}
|
|
|
|
if suite.ID != "quality-context" || suite.Tasks[0].WorkspaceFixture != "fixtures/reader" {
|
|
t.Fatalf("unexpected suite: %#v", suite)
|
|
}
|
|
wantFiles := []string{"internal/reader/a.go", "internal/reader/b.go"}
|
|
if !reflect.DeepEqual(suite.Tasks[0].ExpectedChangedFiles, wantFiles) {
|
|
t.Fatalf("expected files = %#v, want %#v", suite.Tasks[0].ExpectedChangedFiles, wantFiles)
|
|
}
|
|
if got := strings.Join(suite.Tasks[0].VerificationCommands[0].Command, " "); got != "go test ./..." {
|
|
t.Fatalf("command = %q, want go test ./...", got)
|
|
}
|
|
task := suite.Tasks[0]
|
|
if !reflect.DeepEqual(task.Tags, []string{"repo-map", "tool-use"}) {
|
|
t.Fatalf("tags = %#v", task.Tags)
|
|
}
|
|
if task.Difficulty != "medium" {
|
|
t.Fatalf("difficulty = %q", task.Difficulty)
|
|
}
|
|
if !reflect.DeepEqual(task.RequiredTraceEvents, []string{"tool:apply_patch", "tool:read_file"}) {
|
|
t.Fatalf("required trace events = %#v", task.RequiredTraceEvents)
|
|
}
|
|
if !reflect.DeepEqual(task.ContextChecks.RequiredFiles, []string{"internal/reader/b.go"}) {
|
|
t.Fatalf("context required files = %#v", task.ContextChecks.RequiredFiles)
|
|
}
|
|
if !reflect.DeepEqual(task.ContextChecks.ForbiddenFiles, []string{"node_modules/cache.txt"}) {
|
|
t.Fatalf("context forbidden files = %#v", task.ContextChecks.ForbiddenFiles)
|
|
}
|
|
if !reflect.DeepEqual(task.ForbiddenChangedFiles, []string{"docs/generated.log"}) {
|
|
t.Fatalf("forbidden changed files = %#v", task.ForbiddenChangedFiles)
|
|
}
|
|
}
|
|
|
|
func TestLoadSuiteRejectsTrailingJSON(t *testing.T) {
|
|
path := writeSuite(t, `{
|
|
"id": "quality-context",
|
|
"name": "Quality context",
|
|
"tasks": [{
|
|
"id": "edit-reader",
|
|
"name": "Edit reader",
|
|
"prompt": "Update the reader.",
|
|
"workspaceFixture": "fixtures/reader",
|
|
"verificationCommands": [
|
|
{"id": "test", "name": "Tests", "command": ["go", "test", "./..."]}
|
|
],
|
|
"expectedChangedFiles": ["internal/reader/a.go"]
|
|
}]
|
|
}
|
|
{}`)
|
|
|
|
_, err := LoadSuite(path)
|
|
if err == nil {
|
|
t.Fatal("LoadSuite returned nil, want trailing JSON error")
|
|
}
|
|
if !strings.Contains(err.Error(), "trailing JSON") {
|
|
t.Fatalf("LoadSuite error = %q, want trailing JSON", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestSampleSuiteLoads(t *testing.T) {
|
|
matches, err := filepath.Glob(filepath.Join("testdata", "*.json"))
|
|
if err != nil {
|
|
t.Fatalf("glob sample suites: %v", err)
|
|
}
|
|
if len(matches) == 0 {
|
|
t.Fatal("expected at least one sample suite")
|
|
}
|
|
for _, path := range matches {
|
|
t.Run(filepath.Base(path), func(t *testing.T) {
|
|
if _, err := LoadSuite(path); err != nil {
|
|
t.Fatalf("sample suite should load: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
suite, err := LoadSuite(filepath.Join("testdata", "sample_suite.json"))
|
|
if err != nil {
|
|
t.Fatalf("sample suite should load: %v", err)
|
|
}
|
|
if len(suite.Tasks) < 8 || len(suite.Tasks) > 12 {
|
|
t.Fatalf("sample suite tasks = %d, want 8-12", len(suite.Tasks))
|
|
}
|
|
traceTasks := 0
|
|
for _, task := range suite.Tasks {
|
|
if len(task.Tags) == 0 {
|
|
t.Fatalf("sample task %q has no tags", task.ID)
|
|
}
|
|
if task.Difficulty == "" {
|
|
t.Fatalf("sample task %q has no difficulty", task.ID)
|
|
}
|
|
if len(task.ExpectedChangedFiles) == 0 {
|
|
t.Fatalf("sample task %q has no expected changed files", task.ID)
|
|
}
|
|
if len(task.ForbiddenChangedFiles) == 0 {
|
|
t.Fatalf("sample task %q has no forbidden changed files", task.ID)
|
|
}
|
|
if len(task.ContextChecks.RequiredFiles) == 0 {
|
|
t.Fatalf("sample task %q has no required context files", task.ID)
|
|
}
|
|
if len(task.VerificationCommands) == 0 {
|
|
t.Fatalf("sample task %q has no verification commands", task.ID)
|
|
}
|
|
if len(task.RequiredTraceEvents) > 0 {
|
|
traceTasks++
|
|
}
|
|
}
|
|
if traceTasks < 4 {
|
|
t.Fatalf("sample suite trace tasks = %d, want at least 4", traceTasks)
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsMalformedExpectedChangedFiles(t *testing.T) {
|
|
err := Suite{
|
|
ID: "suite",
|
|
Name: "Suite",
|
|
Tasks: []Task{{
|
|
ID: "task",
|
|
Name: "Task",
|
|
Prompt: "Do it",
|
|
WorkspaceFixture: "fixtures/task",
|
|
ExpectedChangedFiles: []string{
|
|
"",
|
|
"/tmp/outside.go",
|
|
"../outside.go",
|
|
`C:\tmp\outside.go`,
|
|
},
|
|
VerificationCommands: []Command{{
|
|
ID: "test",
|
|
Name: "Tests",
|
|
Command: []string{"go", "test", "./..."},
|
|
}},
|
|
}},
|
|
}.Validate()
|
|
|
|
if err == nil {
|
|
t.Fatal("Validate returned nil, want malformed expectedChangedFiles errors")
|
|
}
|
|
message := err.Error()
|
|
for _, want := range []string{
|
|
"expectedChangedFiles[0] must not be empty",
|
|
"expectedChangedFiles[1] must be a relative workspace path",
|
|
"expectedChangedFiles[2] must be a relative workspace path",
|
|
"expectedChangedFiles[3] must be a relative workspace path",
|
|
} {
|
|
if !strings.Contains(message, want) {
|
|
t.Fatalf("expected validation error %q in:\n%s", want, message)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsDuplicateNormalizedExpectedChangedFiles(t *testing.T) {
|
|
err := Suite{
|
|
ID: "suite",
|
|
Name: "Suite",
|
|
Tasks: []Task{{
|
|
ID: "task",
|
|
Name: "Task",
|
|
Prompt: "Do it",
|
|
WorkspaceFixture: "fixtures/task",
|
|
ExpectedChangedFiles: []string{"internal/reader/b.go", "internal/reader/a/../b.go"},
|
|
VerificationCommands: []Command{{
|
|
ID: "test",
|
|
Name: "Tests",
|
|
Command: []string{"go", "test", "./..."},
|
|
}},
|
|
}},
|
|
}.Validate()
|
|
|
|
if err == nil {
|
|
t.Fatal("Validate returned nil, want duplicate expectedChangedFiles error")
|
|
}
|
|
if !strings.Contains(err.Error(), "expectedChangedFiles[1] duplicates expectedChangedFiles[0]") {
|
|
t.Fatalf("unexpected validation error:\n%s", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsMalformedQualityFileLists(t *testing.T) {
|
|
err := Suite{
|
|
ID: "suite",
|
|
Name: "Suite",
|
|
Tasks: []Task{{
|
|
ID: "task",
|
|
Name: "Task",
|
|
Prompt: "Do it",
|
|
WorkspaceFixture: "fixtures/task",
|
|
ExpectedChangedFiles: []string{"internal/reader/a.go"},
|
|
ForbiddenChangedFiles: []string{"../outside.go", "logs/../logs/run.txt", "logs/run.txt"},
|
|
ContextChecks: ContextChecks{
|
|
RequiredFiles: []string{"/tmp/outside.go"},
|
|
ForbiddenFiles: []string{"docs/ok.md", "docs/./ok.md"},
|
|
},
|
|
VerificationCommands: []Command{{
|
|
ID: "test",
|
|
Name: "Tests",
|
|
Command: []string{"go", "test", "./..."},
|
|
}},
|
|
}},
|
|
}.Validate()
|
|
|
|
if err == nil {
|
|
t.Fatal("Validate returned nil, want malformed quality file list errors")
|
|
}
|
|
message := err.Error()
|
|
for _, want := range []string{
|
|
"forbiddenChangedFiles[0] must be a relative workspace path",
|
|
"forbiddenChangedFiles[2] duplicates forbiddenChangedFiles[1]",
|
|
"contextChecks.requiredFiles[0] must be a relative workspace path",
|
|
"contextChecks.forbiddenFiles[1] duplicates contextChecks.forbiddenFiles[0]",
|
|
} {
|
|
if !strings.Contains(message, want) {
|
|
t.Fatalf("expected validation error %q in:\n%s", want, message)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateReportsUsefulErrors(t *testing.T) {
|
|
err := Suite{
|
|
ID: "suite",
|
|
Tasks: []Task{
|
|
{ID: "dup", Prompt: "do it", WorkspaceFixture: "fixtures/a"},
|
|
{ID: "dup", VerificationCommands: []Command{{ID: "test"}}},
|
|
},
|
|
}.Validate()
|
|
|
|
if err == nil {
|
|
t.Fatal("Validate returned nil, want errors")
|
|
}
|
|
message := err.Error()
|
|
for _, want := range []string{
|
|
"suite name is required",
|
|
"tasks[0] name is required",
|
|
"tasks[0] verificationCommands must not be empty",
|
|
"tasks[1] id duplicates tasks[0]",
|
|
"tasks[1] prompt is required",
|
|
"tasks[1] workspaceFixture is required",
|
|
"tasks[1] verificationCommands[0] command must not be empty",
|
|
} {
|
|
if !strings.Contains(message, want) {
|
|
t.Fatalf("expected validation error %q in:\n%s", want, message)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScoreNormalizesSingleTaskExpectedChangedFiles(t *testing.T) {
|
|
report := Score(Suite{
|
|
ID: "quality-context",
|
|
Name: "Quality context",
|
|
Tasks: []Task{{
|
|
ID: "edit-reader",
|
|
Name: "Edit reader",
|
|
ExpectedChangedFiles: []string{"internal/reader/a/../b.go"},
|
|
VerificationCommands: []Command{{
|
|
ID: "test",
|
|
Name: "Tests",
|
|
Command: []string{"go", "test", "./..."},
|
|
}},
|
|
}},
|
|
}, ScoreInput{
|
|
CommandResults: []CommandResult{{ID: "test", ExitCode: 0}},
|
|
ChangedFiles: []string{"internal/reader/b.go"},
|
|
})
|
|
|
|
if !report.OK || report.Status != StatusPass {
|
|
t.Fatalf("expected normalized single-task report to pass, got %#v", report)
|
|
}
|
|
if got, want := report.Results[1].ExpectedFiles, []string{"internal/reader/b.go"}; !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("expected files = %#v, want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestScorePassesWhenCommandsAndChangedFilesMatch(t *testing.T) {
|
|
suite := sampleSuite()
|
|
|
|
report := Score(suite, ScoreInput{
|
|
TaskID: "edit-reader",
|
|
CommandResults: []CommandResult{
|
|
{ID: "test", ExitCode: 0, Stdout: "ok\n"},
|
|
},
|
|
ChangedFiles: []string{"internal/reader/b.go", "internal/reader/a.go"},
|
|
})
|
|
|
|
if !report.OK || report.Status != StatusPass {
|
|
t.Fatalf("expected passing report, got %#v", report)
|
|
}
|
|
if report.Summary.Total != 2 || report.Summary.Passed != 2 {
|
|
t.Fatalf("unexpected summary: %#v", report.Summary)
|
|
}
|
|
if report.Results[0].Kind != ResultCommand || report.Results[0].Status != StatusPass {
|
|
t.Fatalf("unexpected command result: %#v", report.Results)
|
|
}
|
|
if report.Results[1].Kind != ResultChangedFiles || report.Results[1].Status != StatusPass {
|
|
t.Fatalf("unexpected changed-file result: %#v", report.Results)
|
|
}
|
|
}
|
|
|
|
func TestScoreFailsForCommandFailureAndChangedFileMismatch(t *testing.T) {
|
|
report := Score(sampleSuite(), ScoreInput{
|
|
TaskID: "edit-reader",
|
|
CommandResults: []CommandResult{
|
|
{ID: "test", ExitCode: 1, Stderr: "FAIL\n"},
|
|
},
|
|
ChangedFiles: []string{"internal/reader/a.go", "internal/reader/extra.go"},
|
|
})
|
|
|
|
if report.OK || report.Status != StatusFail {
|
|
t.Fatalf("expected failing report, got %#v", report)
|
|
}
|
|
if report.Summary.Failed != 2 {
|
|
t.Fatalf("expected two failed checks, got %#v", report.Summary)
|
|
}
|
|
changed := report.Results[1]
|
|
if !reflect.DeepEqual(changed.MissingFiles, []string{"internal/reader/b.go"}) {
|
|
t.Fatalf("missing files = %#v", changed.MissingFiles)
|
|
}
|
|
if !reflect.DeepEqual(changed.UnexpectedFiles, []string{"internal/reader/extra.go"}) {
|
|
t.Fatalf("unexpected files = %#v", changed.UnexpectedFiles)
|
|
}
|
|
}
|
|
|
|
func TestScoreFailsWhenForbiddenFilesChange(t *testing.T) {
|
|
suite := sampleSuite()
|
|
suite.Tasks[0].ForbiddenChangedFiles = []string{"internal/reader/private.go", "docs/generated.log"}
|
|
|
|
report := Score(suite, ScoreInput{
|
|
TaskID: "edit-reader",
|
|
CommandResults: []CommandResult{
|
|
{ID: "test", ExitCode: 0},
|
|
},
|
|
ChangedFiles: []string{"internal/reader/a.go", "internal/reader/b.go", "internal/reader/private.go"},
|
|
})
|
|
|
|
if report.OK || report.Status != StatusFail {
|
|
t.Fatalf("expected forbidden-file failure, got %#v", report)
|
|
}
|
|
result := findResultByID(t, report.Results, "forbidden_changed_files")
|
|
if result.Status != StatusFail {
|
|
t.Fatalf("forbidden result = %#v", result)
|
|
}
|
|
if !reflect.DeepEqual(result.UnexpectedFiles, []string{"internal/reader/private.go"}) {
|
|
t.Fatalf("forbidden touched files = %#v", result.UnexpectedFiles)
|
|
}
|
|
}
|
|
|
|
func TestScoreErrorsWhenExpectedCommandResultIsMissing(t *testing.T) {
|
|
report := Score(sampleSuite(), ScoreInput{
|
|
TaskID: "edit-reader",
|
|
ChangedFiles: []string{"internal/reader/a.go", "internal/reader/b.go"},
|
|
})
|
|
|
|
if report.OK || report.Status != StatusError {
|
|
t.Fatalf("expected missing command result to error, got %#v", report)
|
|
}
|
|
if report.Summary.Errors != 1 {
|
|
t.Fatalf("expected one error, got %#v", report.Summary)
|
|
}
|
|
if got := report.Results[0].Message; got != "missing command result" {
|
|
t.Fatalf("missing-command message = %q", got)
|
|
}
|
|
if report.Results[0].ExitCode != nil {
|
|
t.Fatalf("missing command result should not expose an exit code: %#v", report.Results[0])
|
|
}
|
|
}
|
|
|
|
func TestScoreErrorsWhenTaskSelectionFails(t *testing.T) {
|
|
suite := Suite{
|
|
ID: "quality-context",
|
|
Name: "Quality context",
|
|
Tasks: []Task{
|
|
sampleSuite().Tasks[0],
|
|
{ID: "other-task", Name: "Other task"},
|
|
},
|
|
}
|
|
|
|
report := Score(suite, ScoreInput{})
|
|
|
|
if report.OK || report.Status != StatusError {
|
|
t.Fatalf("expected task selection error report, got %#v", report)
|
|
}
|
|
if report.Summary.Errors != 1 || report.Error == "" {
|
|
t.Fatalf("unexpected task selection summary/error: %#v", report)
|
|
}
|
|
if !strings.Contains(report.Error, "taskId is required") {
|
|
t.Fatalf("task selection error = %q", report.Error)
|
|
}
|
|
}
|
|
|
|
func TestScoreReportsBlockedRun(t *testing.T) {
|
|
report := Score(sampleSuite(), ScoreInput{
|
|
TaskID: "edit-reader",
|
|
Blocked: true,
|
|
BlockReason: "fixture setup failed",
|
|
ChangedFiles: []string{"internal/reader/a.go"},
|
|
})
|
|
|
|
if report.OK || report.Status != StatusBlocked {
|
|
t.Fatalf("expected blocked report, got %#v", report)
|
|
}
|
|
if report.Summary.Blocked != 2 || report.Results[0].Status != StatusBlocked {
|
|
t.Fatalf("unexpected blocked summary/results: %#v", report)
|
|
}
|
|
if !strings.Contains(report.Results[0].Message, "fixture setup failed") {
|
|
t.Fatalf("expected block reason in result message: %#v", report.Results[0])
|
|
}
|
|
}
|
|
|
|
func TestScoreMarksUnknownCommandsBlockedWhenRunBlocked(t *testing.T) {
|
|
report := Score(sampleSuite(), ScoreInput{
|
|
TaskID: "edit-reader",
|
|
Blocked: true,
|
|
BlockReason: "fixture setup failed",
|
|
CommandResults: []CommandResult{
|
|
{ID: "unexpected", ExitCode: 1},
|
|
},
|
|
})
|
|
|
|
if report.Status != StatusBlocked || report.Summary.Blocked != 3 {
|
|
t.Fatalf("unexpected blocked report: %#v", report)
|
|
}
|
|
if report.Summary.Failed != 0 || report.Summary.Errors != 0 {
|
|
t.Fatalf("blocked run should not report failed/error unknown commands: %#v", report.Summary)
|
|
}
|
|
if got := report.Results[2]; got.ID != "unknown_command.unexpected" || got.Status != StatusBlocked {
|
|
t.Fatalf("unexpected unknown command result: %#v", got)
|
|
}
|
|
}
|
|
|
|
func TestScoreUsesDeterministicOrderingForInjectedInputs(t *testing.T) {
|
|
report := Score(sampleSuite(), ScoreInput{
|
|
TaskID: "edit-reader",
|
|
CommandResults: []CommandResult{
|
|
{ID: "zzz", ExitCode: 1},
|
|
{ID: "test", ExitCode: 0},
|
|
{ID: "aaa", ExitCode: 1},
|
|
{ID: "zzz", ExitCode: 1},
|
|
},
|
|
ChangedFiles: []string{"z.go", "internal/reader/a.go", "m.go"},
|
|
})
|
|
|
|
gotIDs := []string{}
|
|
for _, result := range report.Results {
|
|
gotIDs = append(gotIDs, result.ID)
|
|
}
|
|
wantIDs := []string{"test", "changed_files", "unknown_command.aaa", "unknown_command.zzz"}
|
|
if !reflect.DeepEqual(gotIDs, wantIDs) {
|
|
t.Fatalf("result IDs = %#v, want %#v", gotIDs, wantIDs)
|
|
}
|
|
if !reflect.DeepEqual(report.ChangedFiles, []string{"internal/reader/a.go", "m.go", "z.go"}) {
|
|
t.Fatalf("changed files not sorted: %#v", report.ChangedFiles)
|
|
}
|
|
if !reflect.DeepEqual(report.Results[1].UnexpectedFiles, []string{"m.go", "z.go"}) {
|
|
t.Fatalf("unexpected files not sorted: %#v", report.Results[1].UnexpectedFiles)
|
|
}
|
|
}
|
|
|
|
func TestReportJSONIsStable(t *testing.T) {
|
|
report := Score(sampleSuite(), ScoreInput{
|
|
TaskID: "edit-reader",
|
|
CommandResults: []CommandResult{
|
|
{ID: "test", ExitCode: 0},
|
|
},
|
|
ChangedFiles: []string{"internal/reader/a.go", "internal/reader/b.go"},
|
|
})
|
|
|
|
data, err := json.MarshalIndent(report, "", " ")
|
|
if err != nil {
|
|
t.Fatalf("marshal report: %v", err)
|
|
}
|
|
want := `{
|
|
"contract": "zero.agenteval.report.v1",
|
|
"suiteId": "quality-context",
|
|
"taskId": "edit-reader",
|
|
"status": "pass",
|
|
"ok": true,
|
|
"summary": {
|
|
"total": 2,
|
|
"passed": 2,
|
|
"failed": 0,
|
|
"blocked": 0,
|
|
"errors": 0
|
|
},
|
|
"changedFiles": [
|
|
"internal/reader/a.go",
|
|
"internal/reader/b.go"
|
|
],
|
|
"results": [
|
|
{
|
|
"id": "test",
|
|
"name": "Tests",
|
|
"kind": "command",
|
|
"status": "pass",
|
|
"command": [
|
|
"go",
|
|
"test",
|
|
"./..."
|
|
],
|
|
"exitCode": 0
|
|
},
|
|
{
|
|
"id": "changed_files",
|
|
"name": "Expected changed files",
|
|
"kind": "changed_files",
|
|
"status": "pass",
|
|
"expectedFiles": [
|
|
"internal/reader/a.go",
|
|
"internal/reader/b.go"
|
|
],
|
|
"actualFiles": [
|
|
"internal/reader/a.go",
|
|
"internal/reader/b.go"
|
|
]
|
|
}
|
|
]
|
|
}`
|
|
if string(data) != want {
|
|
t.Fatalf("stable JSON mismatch:\n%s", string(data))
|
|
}
|
|
}
|
|
|
|
func findResultByID(t *testing.T, results []Result, id string) Result {
|
|
t.Helper()
|
|
for _, result := range results {
|
|
if result.ID == id {
|
|
return result
|
|
}
|
|
}
|
|
t.Fatalf("missing result %q in %#v", id, results)
|
|
return Result{}
|
|
}
|
|
|
|
func writeSuite(t *testing.T, content string) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "suite.json")
|
|
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
|
|
t.Fatalf("write suite: %v", err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func sampleSuite() Suite {
|
|
return Suite{
|
|
ID: "quality-context",
|
|
Name: "Quality context",
|
|
Tasks: []Task{{
|
|
ID: "edit-reader",
|
|
Name: "Edit reader",
|
|
Prompt: "Update the reader.",
|
|
WorkspaceFixture: "fixtures/reader",
|
|
VerificationCommands: []Command{{
|
|
ID: "test",
|
|
Name: "Tests",
|
|
Command: []string{"go", "test", "./..."},
|
|
}},
|
|
ExpectedChangedFiles: []string{"internal/reader/a.go", "internal/reader/b.go"},
|
|
}},
|
|
}
|
|
}
|