refactor: modernize stdlib usage and move matchers into dsl
All checks were successful
build-dsl-smoke / Build judge (push) Successful in 16s
build-dsl-smoke / debug / clang / linux (push) Successful in 6s
build-dsl-smoke / debug / gcc / linux (push) Successful in 7s
build-dsl-smoke / release / clang / linux (push) Successful in 8s
build-dsl-smoke / release / gcc / linux (push) Successful in 9s
build-dsl-smoke / sanitized / gcc / linux (push) Successful in 6s
build-dsl-smoke / sanitized / clang / linux (push) Successful in 9s
build-dsl-smoke / debug-valgrind / gcc / linux (push) Successful in 15s
build-dsl-smoke / debug / clang / windows (push) Successful in 15s
build-dsl-smoke / debug / msvc / windows (push) Successful in 19s
build-dsl-smoke / release / clang / windows (push) Successful in 18s
build-dsl-smoke / release / msvc / windows (push) Successful in 18s
build-dsl-smoke / SUMMARY (push) Successful in 4s
All checks were successful
build-dsl-smoke / Build judge (push) Successful in 16s
build-dsl-smoke / debug / clang / linux (push) Successful in 6s
build-dsl-smoke / debug / gcc / linux (push) Successful in 7s
build-dsl-smoke / release / clang / linux (push) Successful in 8s
build-dsl-smoke / release / gcc / linux (push) Successful in 9s
build-dsl-smoke / sanitized / gcc / linux (push) Successful in 6s
build-dsl-smoke / sanitized / clang / linux (push) Successful in 9s
build-dsl-smoke / debug-valgrind / gcc / linux (push) Successful in 15s
build-dsl-smoke / debug / clang / windows (push) Successful in 15s
build-dsl-smoke / debug / msvc / windows (push) Successful in 19s
build-dsl-smoke / release / clang / windows (push) Successful in 18s
build-dsl-smoke / release / msvc / windows (push) Successful in 18s
build-dsl-smoke / SUMMARY (push) Successful in 4s
- Move Matcher types and matching logic from runner/matcher.go into the dsl package as methods on the Matcher types. Runner now calls t.Stdout.Match(label, actual) instead of type-switching via a package-level applyMatcher helper. - Replace custom contains/containsString helpers with slices.Contains in dsl/build.go and runner/compiler.go. - Use maps.Copy instead of manual map copy in BuildConfig.MergeFrom. - Adopt strings.SplitSeq, strings.CutPrefix and the `for range N` loop form in runner/limiter_linux.go. - Ignore example/imdb build artifact.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,5 @@
|
|||||||
example/c-sum/solution
|
example/c-sum/solution
|
||||||
example/solution/solution
|
example/solution/solution
|
||||||
|
.DS_Store
|
||||||
|
*/.DS_Store
|
||||||
|
.claude
|
||||||
|
|||||||
43
dsl/ast.go
43
dsl/ast.go
@@ -1,6 +1,8 @@
|
|||||||
package dsl
|
package dsl
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Build string
|
Build string
|
||||||
@@ -74,42 +76,3 @@ type Test struct {
|
|||||||
Stderr Matcher
|
Stderr Matcher
|
||||||
OutFiles map[string]string
|
OutFiles map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Matcher interface {
|
|
||||||
matcherNode()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExactMatcher struct {
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ExactMatcher) matcherNode() {}
|
|
||||||
|
|
||||||
type ContainsMatcher struct {
|
|
||||||
Substr string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ContainsMatcher) matcherNode() {}
|
|
||||||
|
|
||||||
type RegexMatcher struct {
|
|
||||||
Pattern string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (RegexMatcher) matcherNode() {}
|
|
||||||
|
|
||||||
type NumericEpsMatcher struct {
|
|
||||||
Epsilon float64
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (NumericEpsMatcher) matcherNode() {}
|
|
||||||
|
|
||||||
type AnyOrderMatcher struct {
|
|
||||||
Lines []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (AnyOrderMatcher) matcherNode() {}
|
|
||||||
|
|
||||||
type NoMatcher struct{}
|
|
||||||
|
|
||||||
func (NoMatcher) matcherNode() {}
|
|
||||||
|
|||||||
22
dsl/build.go
22
dsl/build.go
@@ -1,5 +1,10 @@
|
|||||||
package dsl
|
package dsl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
type BuildProfile int
|
type BuildProfile int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -105,9 +110,7 @@ func (dst *BuildConfig) MergeFrom(src *BuildConfig) {
|
|||||||
if dst.Defines == nil {
|
if dst.Defines == nil {
|
||||||
dst.Defines = map[string]string{}
|
dst.Defines = map[string]string{}
|
||||||
}
|
}
|
||||||
for k, v := range src.Defines {
|
maps.Copy(dst.Defines, src.Defines)
|
||||||
dst.Defines[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,24 +135,15 @@ func (b *BuildConfig) Resolve(defaults *BuildConfig, os string) BuildConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *BuildConfig) AppliesTo(os, compiler string) bool {
|
func (b *BuildConfig) AppliesTo(os, compiler string) bool {
|
||||||
if len(b.Platforms) > 0 && !contains(b.Platforms, os) {
|
if len(b.Platforms) > 0 && !slices.Contains(b.Platforms, os) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if len(b.Compilers) > 0 && !contains(b.Compilers, compiler) {
|
if len(b.Compilers) > 0 && !slices.Contains(b.Compilers, compiler) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(xs []string, x string) bool {
|
|
||||||
for _, v := range xs {
|
|
||||||
if v == x {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type ToolchainSpec struct {
|
type ToolchainSpec struct {
|
||||||
Name string
|
Name string
|
||||||
Platforms []string
|
Platforms []string
|
||||||
|
|||||||
@@ -371,8 +371,6 @@ func (l *Lexer) readNumberOrDuration(line, col int) (Token, error) {
|
|||||||
return Token{TOKEN_INT, buf.String(), line, col}, nil
|
return Token{TOKEN_INT, buf.String(), line, col}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryReadSizeSuffix reads memory size suffixes: B, K, KB, KiB, M, MB, MiB, G, GB, GiB.
|
|
||||||
// Units are case-sensitive uppercase to avoid collision with duration "m" (minutes).
|
|
||||||
func (l *Lexer) tryReadSizeSuffix() string {
|
func (l *Lexer) tryReadSizeSuffix() string {
|
||||||
ch, ok := l.peek()
|
ch, ok := l.peek()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package runner
|
package dsl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -7,15 +7,23 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Mond1c/judge/dsl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyMatcher(label string, m dsl.Matcher, actual string) []string {
|
// TODO: maybe move to ast.go
|
||||||
switch m := m.(type) {
|
type Matcher interface {
|
||||||
case dsl.NoMatcher:
|
matcherNode()
|
||||||
return nil
|
|
||||||
case dsl.ExactMatcher:
|
Match(label, actual string) []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExactMatcher struct {
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ExactMatcher) matcherNode() {}
|
||||||
|
|
||||||
|
// TODO: think about pointer receivers
|
||||||
|
func (m ExactMatcher) Match(label, actual string) []string {
|
||||||
if actual != m.Value {
|
if actual != m.Value {
|
||||||
return []string{fmt.Sprintf(
|
return []string{fmt.Sprintf(
|
||||||
"%s mismatch:\n expected: %q\n actual: %q",
|
"%s mismatch:\n expected: %q\n actual: %q",
|
||||||
@@ -23,7 +31,15 @@ func applyMatcher(label string, m dsl.Matcher, actual string) []string {
|
|||||||
)}
|
)}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case dsl.ContainsMatcher:
|
}
|
||||||
|
|
||||||
|
type ContainsMatcher struct {
|
||||||
|
Substr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ContainsMatcher) matcherNode() {}
|
||||||
|
|
||||||
|
func (m ContainsMatcher) Match(label, actual string) []string {
|
||||||
if !strings.Contains(actual, m.Substr) {
|
if !strings.Contains(actual, m.Substr) {
|
||||||
return []string{fmt.Sprintf(
|
return []string{fmt.Sprintf(
|
||||||
"%s: expected to contain %q, got %q",
|
"%s: expected to contain %q, got %q",
|
||||||
@@ -31,11 +47,20 @@ func applyMatcher(label string, m dsl.Matcher, actual string) []string {
|
|||||||
)}
|
)}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case dsl.RegexMatcher:
|
}
|
||||||
|
|
||||||
|
type RegexMatcher struct {
|
||||||
|
Pattern string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (RegexMatcher) matcherNode() {}
|
||||||
|
|
||||||
|
func (m RegexMatcher) Match(label, actual string) []string {
|
||||||
re, err := regexp.Compile(m.Pattern)
|
re, err := regexp.Compile(m.Pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{fmt.Sprintf("%s: invalid regex %q: %v", label, m.Pattern, err)}
|
return []string{fmt.Sprintf("%s: invalid regex %q: %v", label, m.Pattern, err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !re.MatchString(actual) {
|
if !re.MatchString(actual) {
|
||||||
return []string{fmt.Sprintf(
|
return []string{fmt.Sprintf(
|
||||||
"%s: %q does not match regex %q",
|
"%s: %q does not match regex %q",
|
||||||
@@ -43,21 +68,29 @@ func applyMatcher(label string, m dsl.Matcher, actual string) []string {
|
|||||||
)}
|
)}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case dsl.NumericEpsMatcher:
|
|
||||||
errs := matchNumericEps(label, m, actual)
|
|
||||||
return errs
|
|
||||||
|
|
||||||
case dsl.AnyOrderMatcher:
|
|
||||||
return matchAnyOrder(label, m, actual)
|
|
||||||
|
|
||||||
default:
|
|
||||||
return []string{fmt.Sprintf("unknown matcher type %T", m)}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchNumericEps(label string, m dsl.NumericEpsMatcher, actual string) []string {
|
type NumericEpsMatcher struct {
|
||||||
|
Epsilon float64
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NumericEpsMatcher) matcherNode() {}
|
||||||
|
|
||||||
|
func parseNumbers(s string) ([]float64, error) {
|
||||||
|
fields := strings.Fields(s)
|
||||||
|
nums := make([]float64, 0, len(fields))
|
||||||
|
for _, f := range fields {
|
||||||
|
n, err := strconv.ParseFloat(f, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("not a number: %q", f)
|
||||||
|
}
|
||||||
|
nums = append(nums, n)
|
||||||
|
}
|
||||||
|
return nums, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m NumericEpsMatcher) Match(label, actual string) []string {
|
||||||
expectedNums, err := parseNumbers(m.Value)
|
expectedNums, err := parseNumbers(m.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{fmt.Sprintf("%s: cannot parse expected numbers %q: %v", label, m.Value, err)}
|
return []string{fmt.Sprintf("%s: cannot parse expected numbers %q: %v", label, m.Value, err)}
|
||||||
@@ -85,20 +118,21 @@ func matchNumericEps(label string, m dsl.NumericEpsMatcher, actual string) []str
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseNumbers(s string) ([]float64, error) {
|
type AnyOrderMatcher struct {
|
||||||
fields := strings.Fields(s)
|
Lines []string
|
||||||
nums := make([]float64, 0, len(fields))
|
|
||||||
for _, f := range fields {
|
|
||||||
n, err := strconv.ParseFloat(f, 64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("not a number: %q", f)
|
|
||||||
}
|
|
||||||
nums = append(nums, n)
|
|
||||||
}
|
|
||||||
return nums, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchAnyOrder(label string, m dsl.AnyOrderMatcher, actual string) []string {
|
func (AnyOrderMatcher) matcherNode() {}
|
||||||
|
|
||||||
|
func splitLines(s string) []string {
|
||||||
|
s = strings.TrimRight(s, "\n")
|
||||||
|
if s == "" {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return strings.Split(s, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m AnyOrderMatcher) Match(label, actual string) []string {
|
||||||
actualLines := splitLines(actual)
|
actualLines := splitLines(actual)
|
||||||
expectedLines := make([]string, len(m.Lines))
|
expectedLines := make([]string, len(m.Lines))
|
||||||
copy(expectedLines, m.Lines)
|
copy(expectedLines, m.Lines)
|
||||||
@@ -125,10 +159,10 @@ func matchAnyOrder(label string, m dsl.AnyOrderMatcher, actual string) []string
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitLines(s string) []string {
|
type NoMatcher struct{}
|
||||||
s = strings.TrimRight(s, "\n")
|
|
||||||
if s == "" {
|
func (NoMatcher) matcherNode() {}
|
||||||
return []string{}
|
|
||||||
}
|
func (NoMatcher) Match(label, actual string) []string {
|
||||||
return strings.Split(s, "\n")
|
return nil
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package runner
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -161,7 +162,7 @@ func compileMSVC(cfg dsl.BuildConfig, tc Toolchain, outputPath string) []string
|
|||||||
argv = append(argv, "/W4", "/permissive-")
|
argv = append(argv, "/W4", "/permissive-")
|
||||||
}
|
}
|
||||||
|
|
||||||
if containsString(cfg.Sanitize, "address") && cfg.Profile != dsl.ProfileSanitized {
|
if slices.Contains(cfg.Sanitize, "address") && cfg.Profile != dsl.ProfileSanitized {
|
||||||
argv = append(argv, "/fsanitize=address")
|
argv = append(argv, "/fsanitize=address")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,12 +196,3 @@ func sortedKeys(m map[string]string) []string {
|
|||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
return keys
|
return keys
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsString(xs []string, x string) bool {
|
|
||||||
for _, v := range xs {
|
|
||||||
if v == x {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ func createScopeCgroup() (string, error) {
|
|||||||
return "", fmt.Errorf("read /proc/self/cgroup: %w", err)
|
return "", fmt.Errorf("read /proc/self/cgroup: %w", err)
|
||||||
}
|
}
|
||||||
var rel string
|
var rel string
|
||||||
for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") {
|
for line := range strings.SplitSeq(strings.TrimSpace(string(data)), "\n") {
|
||||||
if strings.HasPrefix(line, "0::") {
|
if after, ok := strings.CutPrefix(line, "0::"); ok {
|
||||||
rel = strings.TrimPrefix(line, "0::")
|
rel = after
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ func (l *linuxLimiter) collect() limitStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if data, err := os.ReadFile(filepath.Join(l.cgPath, "memory.events")); err == nil {
|
if data, err := os.ReadFile(filepath.Join(l.cgPath, "memory.events")); err == nil {
|
||||||
for _, line := range strings.Split(string(data), "\n") {
|
for line := range strings.SplitSeq(string(data), "\n") {
|
||||||
fields := strings.Fields(line)
|
fields := strings.Fields(line)
|
||||||
if len(fields) != 2 {
|
if len(fields) != 2 {
|
||||||
continue
|
continue
|
||||||
@@ -217,7 +217,7 @@ func (l *linuxLimiter) cleanup() {
|
|||||||
if l.cgPath == "" {
|
if l.cgPath == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := 0; i < 10; i++ {
|
for range 10 {
|
||||||
err := os.Remove(l.cgPath)
|
err := os.Remove(l.cgPath)
|
||||||
if err == nil || os.IsNotExist(err) {
|
if err == nil || os.IsNotExist(err) {
|
||||||
l.cgPath = ""
|
l.cgPath = ""
|
||||||
|
|||||||
@@ -558,10 +558,10 @@ func (r *Runner) runTest(t *dsl.Test) *TestResult {
|
|||||||
tr.addFailure("exit code: expected %d, got %d", *t.ExitCode, actualCode)
|
tr.addFailure("exit code: expected %d, got %d", *t.ExitCode, actualCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range applyMatcher("stdout", t.Stdout, tr.ActualStdout) {
|
for _, f := range t.Stdout.Match("stdout", tr.ActualStdout) {
|
||||||
tr.addFailure("%s", f)
|
tr.addFailure("%s", f)
|
||||||
}
|
}
|
||||||
for _, f := range applyMatcher("stderr", t.Stderr, tr.ActualStderr) {
|
for _, f := range t.Stderr.Match("stderr", tr.ActualStderr) {
|
||||||
tr.addFailure("%s", f)
|
tr.addFailure("%s", f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user