main shinobi / tests / runner / discover.go
  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}