1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "io/fs"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "sort"
11 "strings"
12)
13
14func resolveexecutable(path string) (string, error) {
15 if filepath.IsAbs(path) {
16 if _, err := os.Stat(path); err != nil {
17 return "", fmt.Errorf("missing make binary %q: %w", path, err)
18 }
19 return path, nil
20 }
21
22 if strings.Contains(path, string(filepath.Separator)) {
23 abs, err := filepath.Abs(path)
24 if err != nil {
25 return "", err
26 }
27 if _, err := os.Stat(abs); err != nil {
28 return "", fmt.Errorf("missing make binary %q: %w", abs, err)
29 }
30 return abs, nil
31 }
32
33 resolved, err := exec.LookPath(path)
34 if err != nil {
35 return "", err
36 }
37 return resolved, nil
38}
39
40func discovercases(root string, selectors []string) ([]testcase, error) {
41 var cases []testcase
42 err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
43 if err != nil {
44 return err
45 }
46 if d.IsDir() || filepath.Base(path) != "test.json" {
47 return nil
48 }
49
50 casedir := filepath.Dir(path)
51 reldir, err := filepath.Rel(root, casedir)
52 if err != nil {
53 return err
54 }
55 reldir = filepath.ToSlash(reldir)
56
57 metabytes, err := os.ReadFile(path)
58 if err != nil {
59 return err
60 }
61 var meta casemetadata
62 if err := json.Unmarshal(metabytes, &meta); err != nil {
63 return fmt.Errorf("decode %s: %w", path, err)
64 }
65
66 if !matchesselectors(reldir, meta.Category, meta.Case, selectors) {
67 return nil
68 }
69
70 mkbytes, err := os.ReadFile(filepath.Join(casedir, "mk"))
71 if err != nil {
72 return err
73 }
74 outbytes, err := os.ReadFile(filepath.Join(casedir, "out"))
75 if err != nil {
76 return err
77 }
78
79 cases = append(cases, testcase{
80 Meta: meta,
81 RelPath: reldir,
82 MakeContents: string(mkbytes),
83 ExpectedOut: string(outbytes),
84 })
85 return nil
86 })
87 if err != nil {
88 return nil, err
89 }
90
91 sort.Slice(cases, func(i, j int) bool {
92 leftsuite := suitename(cases[i])
93 rightsuite := suitename(cases[j])
94 if leftsuite != rightsuite {
95 return leftsuite < rightsuite
96 }
97 if cases[i].Meta.Category == cases[j].Meta.Category {
98 return cases[i].Meta.Case < cases[j].Meta.Case
99 }
100 return cases[i].Meta.Category < cases[j].Meta.Category
101 })
102 return cases, nil
103}
104
105func matchesselectors(relpath, category, casename string, selectors []string) bool {
106 if len(selectors) == 0 {
107 return true
108 }
109
110 fullname := category + "/" + casename
111 for _, selector := range selectors {
112 selector = filepath.ToSlash(selector)
113 if relpath == selector || fullname == selector || category == selector || casename == selector {
114 return true
115 }
116 if strings.HasPrefix(relpath, selector+"/") || strings.HasPrefix(fullname, selector+"/") {
117 return true
118 }
119 if strings.Contains(relpath, selector) || strings.Contains(fullname, selector) {
120 return true
121 }
122 }
123 return false
124}
125
126func orderedcategories(cases []testcase) []string {
127 seen := make(map[string]bool, len(cases))
128 var categories []string
129 for _, tc := range cases {
130 name := categoryname(tc)
131 if seen[name] {
132 continue
133 }
134 seen[name] = true
135 categories = append(categories, name)
136 }
137 return categories
138}
139
140func orderedsuites(cases []testcase) []string {
141 seen := make(map[string]bool, len(cases))
142 var suites []string
143 for _, tc := range cases {
144 name := suitename(tc)
145 if seen[name] {
146 continue
147 }
148 seen[name] = true
149 suites = append(suites, name)
150 }
151 return suites
152}
153
154func categoryname(tc testcase) string {
155 if tc.Meta.Suite == "" {
156 return tc.Meta.Category
157 }
158 return tc.Meta.Suite + "/" + tc.Meta.Category
159}
160
161func suitename(tc testcase) string {
162 return tc.Meta.Suite
163}