135 lines
3.1 KiB
Go
135 lines
3.1 KiB
Go
package runner
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Mond1c/judge/dsl"
|
|
)
|
|
|
|
func applyMatcher(label string, m dsl.Matcher, actual string) []string {
|
|
switch m := m.(type) {
|
|
case dsl.NoMatcher:
|
|
return nil
|
|
case dsl.ExactMatcher:
|
|
if actual != m.Value {
|
|
return []string{fmt.Sprintf(
|
|
"%s mismatch:\n expected: %q\n actual: %q",
|
|
label, m.Value, actual,
|
|
)}
|
|
}
|
|
return nil
|
|
case dsl.ContainsMatcher:
|
|
if !strings.Contains(actual, m.Substr) {
|
|
return []string{fmt.Sprintf(
|
|
"%s: expected to contain %q, got %q",
|
|
label, m.Substr, actual,
|
|
)}
|
|
}
|
|
return nil
|
|
case dsl.RegexMatcher:
|
|
re, err := regexp.Compile(m.Pattern)
|
|
if err != nil {
|
|
return []string{fmt.Sprintf("%s: invalid regex %q: %v", label, m.Pattern, err)}
|
|
}
|
|
if !re.MatchString(actual) {
|
|
return []string{fmt.Sprintf(
|
|
"%s: %q does not match regex %q",
|
|
label, actual, m.Pattern,
|
|
)}
|
|
}
|
|
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 {
|
|
expectedNums, err := parseNumbers(m.Value)
|
|
if err != nil {
|
|
return []string{fmt.Sprintf("%s: cannot parse expected numbers %q: %v", label, m.Value, err)}
|
|
}
|
|
actualNums, err := parseNumbers(actual)
|
|
if err != nil {
|
|
return []string{fmt.Sprintf("%s: cannot parse actual numbers %q: %v", label, actual, err)}
|
|
}
|
|
if len(expectedNums) != len(actualNums) {
|
|
return []string{fmt.Sprintf(
|
|
"%s: expected %d numbers, got %d (expected=%q, actual=%q)",
|
|
label, len(expectedNums), len(actualNums), m.Value, actual,
|
|
)}
|
|
}
|
|
var errs []string
|
|
for i, exp := range expectedNums {
|
|
act := actualNums[i]
|
|
if math.Abs(exp-act) > m.Epsilon {
|
|
errs = append(errs, fmt.Sprintf(
|
|
"%s: number[%d] expected %.10g ± %.10g, got %.10g",
|
|
label, i, exp, m.Epsilon, act,
|
|
))
|
|
}
|
|
}
|
|
return errs
|
|
}
|
|
|
|
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 matchAnyOrder(label string, m dsl.AnyOrderMatcher, actual string) []string {
|
|
actualLines := splitLines(actual)
|
|
expectedLines := make([]string, len(m.Lines))
|
|
copy(expectedLines, m.Lines)
|
|
|
|
sort.Strings(actualLines)
|
|
sort.Strings(expectedLines)
|
|
|
|
if len(actualLines) != len(expectedLines) {
|
|
return []string{fmt.Sprintf(
|
|
"%s anyOrder: expected %d lines, got %d",
|
|
label, len(expectedLines), len(actualLines),
|
|
)}
|
|
}
|
|
|
|
var errs []string
|
|
for i := range expectedLines {
|
|
if actualLines[i] != expectedLines[i] {
|
|
errs = append(errs, fmt.Sprintf(
|
|
"%s anyOrder: line mismatch: expected %q, got %q",
|
|
label, expectedLines[i], actualLines[i],
|
|
))
|
|
}
|
|
}
|
|
return errs
|
|
}
|
|
|
|
func splitLines(s string) []string {
|
|
s = strings.TrimRight(s, "\n")
|
|
if s == "" {
|
|
return []string{}
|
|
}
|
|
return strings.Split(s, "\n")
|
|
}
|