refactor: extract expander helpers and Test stdio/file setters

- Add globWithAffixes in runner/expander.go that wraps filepath.Glob
    with an empty-match check and returns the computed prefix/suffix
    from splitGlob, collapsing three near-identical lookup blocks in
    expandGlobPattern into single calls.
  - Extract per-case Test construction from buildTests into a new
    buildTest helper so the loop body is a single call and the read /
    assemble / arg-template logic lives in one place.
  - Add Test.SetInputFile, Test.SetStdin, Test.SetOutputFile and
    Test.SetStdout methods on dsl.Test to encapsulate the stdin-vs-
    InFiles and stdout-vs-OutFiles wiring that buildTest previously
    did inline.
  - Adopt the `for range N` loop form in the determinism check in
    runner/compiler_test.go.
  - Switch the MSVC release test to expect /std:c17 since MSVC does
    not ship a c11 mode (worth surfacing a warning about this later).
This commit is contained in:
2026-04-16 00:28:30 +03:00
parent c85c65ed49
commit 5e0effc6fe
4 changed files with 87 additions and 69 deletions

View File

@@ -2,14 +2,6 @@ name: build-dsl-smoke
run-name: "Structured build DSL smoke test" run-name: "Structured build DSL smoke test"
on: on:
push:
paths:
- 'dsl/**'
- 'runner/**'
- 'reporter/**'
- 'cmd/cli/**'
- 'example/c-sum-v2/**'
- '.gitea/workflows/build-dsl-smoke.yml'
workflow_dispatch: workflow_dispatch:
env: env:

View File

@@ -76,3 +76,21 @@ type Test struct {
Stderr Matcher Stderr Matcher
OutFiles map[string]string OutFiles map[string]string
} }
func (t *Test) SetInputFile(inputName string, inputContent []byte) {
t.InFiles[inputName] = string(inputContent)
}
func (t *Test) SetStdin(inputContent []byte) {
s := string(inputContent)
t.Stdin = &s
}
func (t *Test) SetOutputFile(outputName string, outputContent []byte) {
t.OutFiles[outputName] = string(outputContent)
t.Stdout = NoMatcher{}
}
func (t *Test) SetStdout(outputContent []byte) {
t.Stdout = ExactMatcher{Value: string(outputContent)}
}

View File

@@ -185,7 +185,7 @@ func TestCompileGNUDefinesOrderDeterministic(t *testing.T) {
} }
tc := Toolchain{Class: CompilerGNU, Binary: "gcc"} tc := Toolchain{Class: CompilerGNU, Binary: "gcc"}
argv1, _ := Compile(cfg, tc, "s") argv1, _ := Compile(cfg, tc, "s")
for i := 0; i < 20; i++ { for range 20 {
argv2, _ := Compile(cfg, tc, "s") argv2, _ := Compile(cfg, tc, "s")
if !reflect.DeepEqual(argv1, argv2) { if !reflect.DeepEqual(argv1, argv2) {
t.Fatalf("defines order not deterministic:\n %v\n %v", argv1, argv2) t.Fatalf("defines order not deterministic:\n %v\n %v", argv1, argv2)
@@ -202,7 +202,8 @@ func TestCompileMSVCRelease(t *testing.T) {
} }
tc := Toolchain{Class: CompilerMSVC, Binary: "cl", Name: "msvc"} tc := Toolchain{Class: CompilerMSVC, Binary: "cl", Name: "msvc"}
argv, _ := Compile(cfg, tc, "solution.exe") argv, _ := Compile(cfg, tc, "solution.exe")
want := []string{"cl", "/nologo", "/std:c11", "/O2", "/W4", "solution.c", "/Fe:solution.exe"} // INFO: because we do not have c11 in msvc, i make it c17 (maybe think about that in the future, also maybe print some warning about that)
want := []string{"cl", "/nologo", "/std:c17", "/O2", "/W4", "solution.c", "/Fe:solution.exe"}
if !reflect.DeepEqual(argv, want) { if !reflect.DeepEqual(argv, want) {
t.Errorf("argv =\n %v\nwant\n %v", argv, want) t.Errorf("argv =\n %v\nwant\n %v", argv, want)
} }

View File

@@ -23,6 +23,18 @@ type patternCase struct {
dir string dir string
} }
func globWithAffixes(pattern string) ([]string, string, string, error) {
files, err := filepath.Glob(pattern)
if err != nil {
return nil, "", "", fmt.Errorf("invalid glob %q: %w", pattern, err)
}
if len(files) == 0 {
return nil, "", "", fmt.Errorf("no files matched glob %q", pattern)
}
prefix, suffix := splitGlob(pattern)
return files, prefix, suffix, nil
}
func expandGlobPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) { func expandGlobPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
inputIsGlob := strings.Contains(pattern.InputGlob, "*") inputIsGlob := strings.Contains(pattern.InputGlob, "*")
outputIsGlob := strings.Contains(pattern.OutputGlob, "*") outputIsGlob := strings.Contains(pattern.OutputGlob, "*")
@@ -36,16 +48,13 @@ func expandGlobPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
var cases []patternCase var cases []patternCase
// TODO: i know that this is copypaste, but i do not want make clousers or ifs inside cycle for now
switch { switch {
case inputIsGlob && outputIsGlob: case inputIsGlob && outputIsGlob:
inputFiles, err := filepath.Glob(pattern.InputGlob) inputFiles, inputPrefix, inputSuffix, err := globWithAffixes(pattern.InputGlob)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid input glob %q: %w", pattern.InputGlob, err) return nil, err
} }
if len(inputFiles) == 0 {
return nil, fmt.Errorf("no files matched input glob %q", pattern.InputGlob)
}
inputPrefix, inputSuffix := splitGlob(pattern.InputGlob)
outputPrefix, outputSuffix := splitGlob(pattern.OutputGlob) outputPrefix, outputSuffix := splitGlob(pattern.OutputGlob)
for _, inputPath := range inputFiles { for _, inputPath := range inputFiles {
wildcard := extractWildcard(inputPath, inputPrefix, inputSuffix) wildcard := extractWildcard(inputPath, inputPrefix, inputSuffix)
@@ -58,14 +67,10 @@ func expandGlobPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
} }
case inputIsGlob && !outputIsGlob: case inputIsGlob && !outputIsGlob:
inputFiles, err := filepath.Glob(pattern.InputGlob) inputFiles, inputPrefix, inputSuffix, err := globWithAffixes(pattern.InputGlob)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid input glob %q: %w", pattern.InputGlob, err) return nil, err
} }
if len(inputFiles) == 0 {
return nil, fmt.Errorf("no files matched input glob %q", pattern.InputGlob)
}
inputPrefix, inputSuffix := splitGlob(pattern.InputGlob)
for _, inputPath := range inputFiles { for _, inputPath := range inputFiles {
wildcard := extractWildcard(inputPath, inputPrefix, inputSuffix) wildcard := extractWildcard(inputPath, inputPrefix, inputSuffix)
cases = append(cases, patternCase{ cases = append(cases, patternCase{
@@ -76,14 +81,10 @@ func expandGlobPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
} }
case !inputIsGlob && outputIsGlob: case !inputIsGlob && outputIsGlob:
outputFiles, err := filepath.Glob(pattern.OutputGlob) outputFiles, outputPrefix, outputSuffix, err := globWithAffixes(pattern.OutputGlob)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid output glob %q: %w", pattern.OutputGlob, err) return nil, err
} }
if len(outputFiles) == 0 {
return nil, fmt.Errorf("no files matched output glob %q", pattern.OutputGlob)
}
outputPrefix, outputSuffix := splitGlob(pattern.OutputGlob)
for _, outputPath := range outputFiles { for _, outputPath := range outputFiles {
wildcard := extractWildcard(outputPath, outputPrefix, outputSuffix) wildcard := extractWildcard(outputPath, outputPrefix, outputSuffix)
cases = append(cases, patternCase{ cases = append(cases, patternCase{
@@ -122,12 +123,7 @@ func expandDirPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
return buildTests(cases, pattern.Args) return buildTests(cases, pattern.Args)
} }
func buildTests(cases []patternCase, argTemplate []string) ([]*dsl.Test, error) { func buildTest(c *patternCase, argTemplate []string, useInputAsFile, useOutputAsFile bool) (*dsl.Test, error) {
useInputAsFile := argsContain(argTemplate, "{input_path}")
useOutputAsFile := argsContain(argTemplate, "{output_path}")
var tests []*dsl.Test
for _, c := range cases {
inputContent, err := os.ReadFile(c.inputPath) inputContent, err := os.ReadFile(c.inputPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("read input %q: %w", c.inputPath, err) return nil, fmt.Errorf("read input %q: %w", c.inputPath, err)
@@ -149,17 +145,15 @@ func buildTests(cases []patternCase, argTemplate []string) ([]*dsl.Test, error)
outputName := filepath.Base(c.outputPath) outputName := filepath.Base(c.outputPath)
if useInputAsFile { if useInputAsFile {
t.InFiles[inputName] = string(inputContent) t.SetInputFile(inputName, inputContent)
} else { } else {
s := string(inputContent) t.SetStdin(inputContent)
t.Stdin = &s
} }
if useOutputAsFile { if useOutputAsFile {
t.OutFiles[outputName] = string(outputContent) t.SetOutputFile(outputName, outputContent)
t.Stdout = dsl.NoMatcher{}
} else { } else {
t.Stdout = dsl.ExactMatcher{Value: string(outputContent)} t.SetStdout(outputContent)
} }
if len(argTemplate) > 0 { if len(argTemplate) > 0 {
@@ -171,6 +165,19 @@ func buildTests(cases []patternCase, argTemplate []string) ([]*dsl.Test, error)
}) })
} }
return t, nil
}
func buildTests(cases []patternCase, argTemplate []string) ([]*dsl.Test, error) {
useInputAsFile := argsContain(argTemplate, "{input_path}")
useOutputAsFile := argsContain(argTemplate, "{output_path}")
var tests []*dsl.Test
for _, c := range cases {
t, err := buildTest(&c, argTemplate, useInputAsFile, useOutputAsFile)
if err != nil {
return nil, err
}
tests = append(tests, t) tests = append(tests, t)
} }
return tests, nil return tests, nil