- 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).
219 lines
5.5 KiB
Go
219 lines
5.5 KiB
Go
package runner
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/Mond1c/judge/dsl"
|
|
)
|
|
|
|
func expandPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
|
|
if pattern.IsDirMode() {
|
|
return expandDirPattern(pattern)
|
|
}
|
|
return expandGlobPattern(pattern)
|
|
}
|
|
|
|
type patternCase struct {
|
|
name string
|
|
inputPath string
|
|
outputPath 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) {
|
|
inputIsGlob := strings.Contains(pattern.InputGlob, "*")
|
|
outputIsGlob := strings.Contains(pattern.OutputGlob, "*")
|
|
|
|
if pattern.InputGlob == "" && pattern.OutputGlob == "" {
|
|
return nil, fmt.Errorf("pattern needs at least one of input/output/dirs")
|
|
}
|
|
if !inputIsGlob && !outputIsGlob {
|
|
return nil, fmt.Errorf("pattern needs at least one glob field (input or output must contain *)")
|
|
}
|
|
|
|
var cases []patternCase
|
|
|
|
// TODO: i know that this is copypaste, but i do not want make clousers or ifs inside cycle for now
|
|
switch {
|
|
case inputIsGlob && outputIsGlob:
|
|
inputFiles, inputPrefix, inputSuffix, err := globWithAffixes(pattern.InputGlob)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outputPrefix, outputSuffix := splitGlob(pattern.OutputGlob)
|
|
for _, inputPath := range inputFiles {
|
|
wildcard := extractWildcard(inputPath, inputPrefix, inputSuffix)
|
|
outputPath := outputPrefix + wildcard + outputSuffix
|
|
cases = append(cases, patternCase{
|
|
name: wildcard,
|
|
inputPath: inputPath,
|
|
outputPath: outputPath,
|
|
})
|
|
}
|
|
|
|
case inputIsGlob && !outputIsGlob:
|
|
inputFiles, inputPrefix, inputSuffix, err := globWithAffixes(pattern.InputGlob)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, inputPath := range inputFiles {
|
|
wildcard := extractWildcard(inputPath, inputPrefix, inputSuffix)
|
|
cases = append(cases, patternCase{
|
|
name: wildcard,
|
|
inputPath: inputPath,
|
|
outputPath: pattern.OutputGlob,
|
|
})
|
|
}
|
|
|
|
case !inputIsGlob && outputIsGlob:
|
|
outputFiles, outputPrefix, outputSuffix, err := globWithAffixes(pattern.OutputGlob)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, outputPath := range outputFiles {
|
|
wildcard := extractWildcard(outputPath, outputPrefix, outputSuffix)
|
|
cases = append(cases, patternCase{
|
|
name: wildcard,
|
|
inputPath: pattern.InputGlob,
|
|
outputPath: outputPath,
|
|
})
|
|
}
|
|
}
|
|
|
|
return buildTests(cases, pattern.Args)
|
|
}
|
|
|
|
func expandDirPattern(pattern *dsl.Pattern) ([]*dsl.Test, error) {
|
|
dirs, err := filepath.Glob(pattern.DirsGlob)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid dirs glob %q: %w", pattern.DirsGlob, err)
|
|
}
|
|
if len(dirs) == 0 {
|
|
return nil, fmt.Errorf("no directories matched %q", pattern.DirsGlob)
|
|
}
|
|
|
|
var cases []patternCase
|
|
for _, dir := range dirs {
|
|
info, err := os.Stat(dir)
|
|
if err != nil || !info.IsDir() {
|
|
continue
|
|
}
|
|
cases = append(cases, patternCase{
|
|
name: filepath.Base(dir),
|
|
inputPath: filepath.Join(dir, pattern.InputFile),
|
|
outputPath: filepath.Join(dir, pattern.OutputFile),
|
|
dir: dir,
|
|
})
|
|
}
|
|
return buildTests(cases, pattern.Args)
|
|
}
|
|
|
|
func buildTest(c *patternCase, argTemplate []string, useInputAsFile, useOutputAsFile bool) (*dsl.Test, error) {
|
|
inputContent, err := os.ReadFile(c.inputPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read input %q: %w", c.inputPath, err)
|
|
}
|
|
outputContent, err := os.ReadFile(c.outputPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read output %q: %w", c.outputPath, err)
|
|
}
|
|
|
|
t := &dsl.Test{
|
|
Name: fmt.Sprintf("pattern:%s", c.name),
|
|
Env: map[string]string{},
|
|
InFiles: map[string]string{},
|
|
OutFiles: map[string]string{},
|
|
Stderr: dsl.NoMatcher{},
|
|
}
|
|
|
|
inputName := filepath.Base(c.inputPath)
|
|
outputName := filepath.Base(c.outputPath)
|
|
|
|
if useInputAsFile {
|
|
t.SetInputFile(inputName, inputContent)
|
|
} else {
|
|
t.SetStdin(inputContent)
|
|
}
|
|
|
|
if useOutputAsFile {
|
|
t.SetOutputFile(outputName, outputContent)
|
|
} else {
|
|
t.SetStdout(outputContent)
|
|
}
|
|
|
|
if len(argTemplate) > 0 {
|
|
t.Args = substituteArgs(argTemplate, map[string]string{
|
|
"{input_path}": inputName,
|
|
"{output_path}": outputName,
|
|
"{name}": c.name,
|
|
"{dir}": c.dir,
|
|
})
|
|
}
|
|
|
|
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)
|
|
}
|
|
return tests, nil
|
|
}
|
|
|
|
func argsContain(args []string, placeholder string) bool {
|
|
for _, a := range args {
|
|
if strings.Contains(a, placeholder) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func substituteArgs(template []string, vars map[string]string) []string {
|
|
out := make([]string, len(template))
|
|
for i, a := range template {
|
|
for k, v := range vars {
|
|
a = strings.ReplaceAll(a, k, v)
|
|
}
|
|
out[i] = a
|
|
}
|
|
return out
|
|
}
|
|
|
|
func splitGlob(pattern string) (prefix, suffix string) {
|
|
before, after, found := strings.Cut(pattern, "*")
|
|
if !found {
|
|
return pattern, ""
|
|
}
|
|
return before, after
|
|
}
|
|
|
|
func extractWildcard(path, prefix, suffix string) string {
|
|
s := strings.TrimPrefix(path, prefix)
|
|
s = strings.TrimSuffix(s, suffix)
|
|
return s
|
|
}
|