package check import ( "encoding/json" "os" "path/filepath" "strings" "testing" "amadan.net/rastrillo/fiesta" "amadan.net/rastrillo/fiesta/example" ) // The seeded fixtures: the pages rastrillo's browser drives built by // hand at the seed commit, written out once under testdata/fixtures/ // and listed in index.json by the file, builder and case they came // from. A drive ported here reads its page through fixture rather // than retyping it, so the page it drives is the page the rastrillo // drive drove. // fixtureRow is one entry of testdata/fixtures/index.json. A row // names either a path under testdata/fixtures/ or an example state // that carries the same markup; the two reference rows have no path. type fixtureRow struct { File string `json:"file"` Builder string `json:"builder"` Case string `json:"case"` Path string `json:"path"` Kind string `json:"kind"` Request string `json:"request"` Example string `json:"example"` Note string `json:"note"` } // fixtureDir is where the index and the files live, under check/. const fixtureDir = "testdata/fixtures" // fixtureIndex reads index.json. func fixtureIndex(t *testing.T) []fixtureRow { t.Helper() b, err := os.ReadFile(filepath.Join(fixtureDir, "index.json")) if err != nil { t.Fatal(err) } var rows []fixtureRow if err := json.Unmarshal(b, &rows); err != nil { t.Fatalf("%s/index.json: %v", fixtureDir, err) } return rows } // fixture returns the contents of the fixture file the index lists for // the {file, builder, case} row: the body or the whole document, as // the row's kind says, exactly as the rastrillo builder wrote it. It fails // the test when the row is missing, when the row names an example // rather than a path, or when the file is absent. func fixture(t *testing.T, file, builder, kase string) string { t.Helper() for _, r := range fixtureIndex(t) { if r.File != file || r.Builder != builder || r.Case != kase { continue } if r.Path == "" { t.Fatalf("fixture %s %s %q has no path (it names example %q); read it through the example package", file, builder, kase, r.Example) } b, err := os.ReadFile(filepath.Join(fixtureDir, filepath.FromSlash(r.Path))) if err != nil { t.Fatalf("fixture %s %s %q: %v", file, builder, kase, err) } return string(b) } t.Fatalf("fixture %s %s %q is not in %s/index.json", file, builder, kase, fixtureDir) return "" } // fixtureExample returns the markup of the example state the index // names for the {file, builder, case} row: the two rows whose rastrillo // page was a styleguide sample carry "example/state" instead of a // path, and read through the example package. It fails the test when // the row is missing, when the row names a path rather than an // example, or when the state cannot be read. func fixtureExample(t *testing.T, file, builder, kase string) string { t.Helper() for _, r := range fixtureIndex(t) { if r.File != file || r.Builder != builder || r.Case != kase { continue } if r.Example == "" { t.Fatalf("fixture %s %s %q names a path (%s), not an example; read it through fixture", file, builder, kase, r.Path) } name, state, ok := strings.Cut(r.Example, "/") if !ok { t.Fatalf("fixture %s %s %q names example %q, which is not example/state", file, builder, kase, r.Example) } markup, err := example.Markup(fiesta.FS(), name, state) if err != nil { t.Fatalf("fixture %s %s %q: %v", file, builder, kase, err) } return markup } t.Fatalf("fixture %s %s %q is not in %s/index.json", file, builder, kase, fixtureDir) return "" } // TestEveryFixtureRowResolves: every row that names a path names a // file that exists and is not empty, and the rows without a path are // the example references, which name an example. The count is floored // at 100 so an index that lost its rows could not pass by listing // nothing. func TestEveryFixtureRowResolves(t *testing.T) { rows := fixtureIndex(t) if len(rows) < 100 { t.Fatalf("%s/index.json lists %d rows; the seed wrote 137", fixtureDir, len(rows)) } seen := map[[3]string]bool{} for _, r := range rows { key := [3]string{r.File, r.Builder, r.Case} if seen[key] { t.Errorf("%s %s %q is listed twice", r.File, r.Builder, r.Case) } seen[key] = true if r.Path == "" { if r.Example == "" { t.Errorf("%s %s %q has neither a path nor an example", r.File, r.Builder, r.Case) } continue } info, err := os.Stat(filepath.Join(fixtureDir, filepath.FromSlash(r.Path))) if err != nil { t.Errorf("%s %s %q: %v", r.File, r.Builder, r.Case, err) continue } if info.Size() == 0 { t.Errorf("%s %s %q: %s is empty", r.File, r.Builder, r.Case, r.Path) } if got := fixture(t, r.File, r.Builder, r.Case); int64(len(got)) != info.Size() { t.Errorf("%s %s %q: fixture returned %d bytes of a %d-byte file", r.File, r.Builder, r.Case, len(got), info.Size()) } } }