test: expand dsl/runner coverage and add go-test CI workflow
All checks were successful
go-test / go test (push) Successful in 56s
All checks were successful
go-test / go test (push) Successful in 56s
- Add dsl/matcher_test.go covering ExactMatcher, ContainsMatcher,
RegexMatcher, NumericEpsMatcher, AnyOrderMatcher and NoMatcher —
previously 0% — including epsilon, count mismatch, unparsable
numbers, invalid regex, and splitLines edge cases.
- Add dsl/ast_test.go for the new Test.SetInputFile / SetStdin /
SetOutputFile / SetStdout helpers and Pattern.IsDirMode.
- Add dsl/build_string_test.go covering BuildProfile.String,
WarningLevel.String and BuildConfig.MergeFrom (wrapper, defines
into nil map, defines override existing, nil src).
- Add dsl/merge_test.go driving mergeFiles to 100%: legacy build
fields, duplicate toolchain/build/group from include, binary /
sources / normalize_crlf / trim_trailing_ws propagation, local
overrides of timeout and memory_limit.
- Add dsl/parser_features_test.go for parseTest / parseGroup happy
paths that were missing: file/outFile, env, wrapper, per-test
timeout/memory overrides, non-zero exitCode, scoring partial /
all_or_none and unknown scoring.
- Add dsl/parser_errors_test.go, a 54-case table-driven test that
hits every `expect(...)` error branch in parseGroup and parseTest
(missing LPAREN/RPAREN/LBRACE/RBRACE/ASSIGN, wrong token types on
weight/timeout/memory_limit/scoring/env/wrapper/file/outFile, and
unclosed blocks).
- Add dsl/parser_misc_test.go covering parsePattern dir-mode with
args, unknown pattern field, non-ident in pattern, top-level
binary / sources / normalize_crlf / trim_trailing_ws / bare-int
memory_limit, parseBool invalid ident and non-ident, matcher
without an operator, validateBuilds legacy+structured conflict.
- Add dsl/build_parser_test.go covering every BuildConfig field
(sources, includes, sanitize, link, extra, platforms, compilers,
defines), OS overrides on named builds, nested / duplicate OS
override errors, unknown build / profile / warnings / platform,
missing = on assign-string and assign-string-list, define(...)
error cases, and parseToolchainsBlock (duplicate name, missing
platforms, bad name token, unknown field, unknown compiler class,
binary and class propagation).
- Add dsl/lexer_test.go for Token.String, TokenType.String UNKNOWN
branch, line comments, unterminated string and heredoc, unknown
escape sequence, escape decoding, unexpected character, every
K/M/G/KiB/MiB/GiB size suffix, ms/s/m duration suffixes, negative
integer lexing and float literals.
- Extend runner/result_test.go with Status.String and
TestResult.addFailure (both previously 0%).
- Add .gitea/workflows/go-test.yml running `go vet` and
`go test -race -coverprofile=coverage.out ./...` on push,
pull_request and manual dispatch, uploading coverage.out as an
artifact.
Coverage: dsl 60.5% -> 85%+, runner 29.0% -> 30.5%.
This commit is contained in:
36
.gitea/workflows/go-test.yml
Normal file
36
.gitea/workflows/go-test.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
name: go-test
|
||||
run-name: "Go unit tests"
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: go test
|
||||
runs-on: Linux-Runner
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.26'
|
||||
|
||||
- name: go vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: go test
|
||||
run: go test -race -coverprofile=coverage.out ./...
|
||||
|
||||
- name: Coverage summary
|
||||
run: go tool cover -func=coverage.out | tail -20
|
||||
|
||||
- name: Upload coverage
|
||||
if: ${{ always() }}
|
||||
uses: https://github.com/christopherHX/gitea-upload-artifact@v4
|
||||
with:
|
||||
name: coverage
|
||||
path: coverage.out
|
||||
retention-days: 7
|
||||
75
dsl/ast_test.go
Normal file
75
dsl/ast_test.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package dsl
|
||||
|
||||
import "testing"
|
||||
|
||||
func newTest() *Test {
|
||||
return &Test{
|
||||
InFiles: map[string]string{},
|
||||
OutFiles: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetInputFile(t *testing.T) {
|
||||
tst := newTest()
|
||||
tst.SetInputFile("input.txt", []byte("hello"))
|
||||
if got := tst.InFiles["input.txt"]; got != "hello" {
|
||||
t.Errorf("InFiles[input.txt] = %q, want %q", got, "hello")
|
||||
}
|
||||
if tst.Stdin != nil {
|
||||
t.Errorf("Stdin should remain nil, got %v", *tst.Stdin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetStdin(t *testing.T) {
|
||||
tst := newTest()
|
||||
tst.SetStdin([]byte("data\n"))
|
||||
if tst.Stdin == nil {
|
||||
t.Fatal("Stdin is nil")
|
||||
}
|
||||
if *tst.Stdin != "data\n" {
|
||||
t.Errorf("Stdin = %q, want %q", *tst.Stdin, "data\n")
|
||||
}
|
||||
if len(tst.InFiles) != 0 {
|
||||
t.Errorf("InFiles should be empty, got %v", tst.InFiles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetOutputFileSetsNoMatcher(t *testing.T) {
|
||||
tst := newTest()
|
||||
tst.SetOutputFile("out.txt", []byte("result"))
|
||||
if got := tst.OutFiles["out.txt"]; got != "result" {
|
||||
t.Errorf("OutFiles[out.txt] = %q, want %q", got, "result")
|
||||
}
|
||||
if _, ok := tst.Stdout.(NoMatcher); !ok {
|
||||
t.Errorf("Stdout = %T, want NoMatcher", tst.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetStdoutSetsExactMatcher(t *testing.T) {
|
||||
tst := newTest()
|
||||
tst.SetStdout([]byte("expected\n"))
|
||||
m, ok := tst.Stdout.(ExactMatcher)
|
||||
if !ok {
|
||||
t.Fatalf("Stdout = %T, want ExactMatcher", tst.Stdout)
|
||||
}
|
||||
if m.Value != "expected\n" {
|
||||
t.Errorf("ExactMatcher.Value = %q, want %q", m.Value, "expected\n")
|
||||
}
|
||||
if len(tst.OutFiles) != 0 {
|
||||
t.Errorf("OutFiles should be empty, got %v", tst.OutFiles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDirModeTrue(t *testing.T) {
|
||||
p := &Pattern{DirsGlob: "tests/*"}
|
||||
if !p.IsDirMode() {
|
||||
t.Error("IsDirMode() = false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDirModeFalse(t *testing.T) {
|
||||
p := &Pattern{InputGlob: "tests/*.in"}
|
||||
if p.IsDirMode() {
|
||||
t.Error("IsDirMode() = true, want false")
|
||||
}
|
||||
}
|
||||
379
dsl/build_parser_test.go
Normal file
379
dsl/build_parser_test.go
Normal file
@@ -0,0 +1,379 @@
|
||||
package dsl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const buildDefaultsPrefix = `
|
||||
build_defaults {
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources = "*.c"
|
||||
output = "sol"
|
||||
warnings = strict
|
||||
}
|
||||
`
|
||||
|
||||
func TestBuildAllFieldsParsed(t *testing.T) {
|
||||
src := `
|
||||
build_defaults {
|
||||
language = "cpp"
|
||||
standard = "c++17"
|
||||
sources = "a.cpp" "b.cpp"
|
||||
includes = "inc" "inc2"
|
||||
output = "sol"
|
||||
warnings = pedantic
|
||||
wrapper = "timeout"
|
||||
sanitize = "address" "undefined"
|
||||
link = "-lm"
|
||||
extra = "-g"
|
||||
platforms = "linux" "darwin"
|
||||
compilers = "gcc" "clang"
|
||||
define("DEBUG") = "1"
|
||||
define("VERSION") = "2"
|
||||
}
|
||||
|
||||
build "release" {
|
||||
profile = release
|
||||
}
|
||||
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
bd := f.BuildDefaults
|
||||
if bd == nil {
|
||||
t.Fatal("no build_defaults")
|
||||
}
|
||||
if bd.Language != "cpp" || bd.Standard != "c++17" || bd.Output != "sol" {
|
||||
t.Errorf("basic fields wrong: %+v", bd)
|
||||
}
|
||||
if bd.Warnings != WarningsPedantic {
|
||||
t.Errorf("Warnings = %v", bd.Warnings)
|
||||
}
|
||||
if bd.Wrapper != "timeout" {
|
||||
t.Errorf("Wrapper = %q", bd.Wrapper)
|
||||
}
|
||||
if len(bd.Sources) != 2 || len(bd.Includes) != 2 || len(bd.Sanitize) != 2 {
|
||||
t.Errorf("lists: sources=%v includes=%v sanitize=%v", bd.Sources, bd.Includes, bd.Sanitize)
|
||||
}
|
||||
if len(bd.Link) != 1 || len(bd.Extra) != 1 {
|
||||
t.Errorf("link/extra: %v / %v", bd.Link, bd.Extra)
|
||||
}
|
||||
if len(bd.Platforms) != 2 || len(bd.Compilers) != 2 {
|
||||
t.Errorf("platforms/compilers: %v / %v", bd.Platforms, bd.Compilers)
|
||||
}
|
||||
if bd.Defines["DEBUG"] != "1" || bd.Defines["VERSION"] != "2" {
|
||||
t.Errorf("Defines = %v", bd.Defines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOSOverrides(t *testing.T) {
|
||||
src := `
|
||||
build_defaults {
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources = "main.c"
|
||||
output = "sol"
|
||||
warnings = strict
|
||||
}
|
||||
|
||||
build "release" {
|
||||
profile = release
|
||||
linux { extra = "-pthread" }
|
||||
windows { extra = "/MT" }
|
||||
darwin { extra = "-framework CoreFoundation" }
|
||||
}
|
||||
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
b := f.Builds[0]
|
||||
if b.Linux == nil || b.Windows == nil || b.Darwin == nil {
|
||||
t.Errorf("OS overrides missing: %+v", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildOSNestedForbidden(t *testing.T) {
|
||||
src := buildDefaultsPrefix + `
|
||||
build "r" {
|
||||
profile = release
|
||||
linux {
|
||||
darwin { extra = "-x" }
|
||||
}
|
||||
}
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "nested inside another OS override") {
|
||||
t.Errorf("want nested OS override error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildDuplicateOSOverride(t *testing.T) {
|
||||
src := buildDefaultsPrefix + `
|
||||
build "r" {
|
||||
profile = release
|
||||
linux { extra = "-a" }
|
||||
linux { extra = "-b" }
|
||||
}
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "duplicate linux override") {
|
||||
t.Errorf("want duplicate linux override, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildUnknownField(t *testing.T) {
|
||||
src := `
|
||||
build_defaults {
|
||||
bogus = "x"
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources = "main.c"
|
||||
output = "sol"
|
||||
warnings = strict
|
||||
}
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown field") {
|
||||
t.Errorf("want unknown field error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildUnknownProfile(t *testing.T) {
|
||||
src := `
|
||||
build_defaults {
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources = "main.c"
|
||||
output = "sol"
|
||||
warnings = strict
|
||||
}
|
||||
build "weird" { profile = bogus }
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown profile") {
|
||||
t.Errorf("want unknown profile error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildUnknownWarnings(t *testing.T) {
|
||||
src := `
|
||||
build_defaults {
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources = "main.c"
|
||||
output = "sol"
|
||||
warnings = bogus
|
||||
}
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown warnings level") {
|
||||
t.Errorf("want unknown warnings error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWarningsDefaultAndStrict(t *testing.T) {
|
||||
src := `
|
||||
build_defaults {
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources = "main.c"
|
||||
output = "sol"
|
||||
warnings = default
|
||||
}
|
||||
build "r" {
|
||||
warnings = strict
|
||||
}
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if f.BuildDefaults.Warnings != WarningsDefault {
|
||||
t.Errorf("defaults warnings = %v", f.BuildDefaults.Warnings)
|
||||
}
|
||||
if f.Builds[0].Warnings != WarningsStrict {
|
||||
t.Errorf("build warnings = %v", f.Builds[0].Warnings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildInvalidPlatform(t *testing.T) {
|
||||
src := buildDefaultsPrefix + `
|
||||
build "r" {
|
||||
profile = release
|
||||
platforms = "bsd"
|
||||
}
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown platform") {
|
||||
t.Errorf("want unknown platform error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAssignStringMissingAssign(t *testing.T) {
|
||||
src := `
|
||||
build_defaults {
|
||||
language "c"
|
||||
standard = "c11"
|
||||
sources = "main.c"
|
||||
output = "sol"
|
||||
warnings = strict
|
||||
}
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "expected") {
|
||||
t.Errorf("want expected = error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAssignStringListMissingAssign(t *testing.T) {
|
||||
src := `
|
||||
build_defaults {
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources "main.c"
|
||||
output = "sol"
|
||||
warnings = strict
|
||||
}
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "expected") {
|
||||
t.Errorf("want expected = error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildDefineErrors(t *testing.T) {
|
||||
cases := []string{
|
||||
`build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict define "K" = "v" }`,
|
||||
`build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict define(K) = "v" }`,
|
||||
`build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict define("K" = "v" }`,
|
||||
`build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict define("K") "v" }`,
|
||||
`build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict define("K") = K }`,
|
||||
}
|
||||
for i, src := range cases {
|
||||
full := src + "\ngroup(\"g\") { weight = 1.0 test(\"t\") { stdout = \"\" } }"
|
||||
if _, _, err := Parse(full); err == nil {
|
||||
t.Errorf("case %d: expected error", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildNonIdentInBlock(t *testing.T) {
|
||||
src := `
|
||||
build_defaults { "not-an-ident" = "x" }
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "unexpected token") {
|
||||
t.Errorf("want unexpected token error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToolchainsDuplicateInSameBlock(t *testing.T) {
|
||||
src := `
|
||||
toolchains {
|
||||
gcc { platforms = "linux" }
|
||||
gcc { platforms = "darwin" }
|
||||
}
|
||||
build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict }
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "duplicate toolchain") {
|
||||
t.Errorf("want duplicate toolchain error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToolchainsMissingPlatforms(t *testing.T) {
|
||||
src := `
|
||||
toolchains {
|
||||
gcc { binary = "gcc-13" }
|
||||
}
|
||||
build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict }
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "platforms is required") {
|
||||
t.Errorf("want platforms-required error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToolchainsBadName(t *testing.T) {
|
||||
src := `
|
||||
toolchains {
|
||||
42 { platforms = "linux" }
|
||||
}
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "expected toolchain name") {
|
||||
t.Errorf("want toolchain name error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToolchainsUnknownField(t *testing.T) {
|
||||
src := `
|
||||
toolchains {
|
||||
gcc { platforms = "linux" bogus = "x" }
|
||||
}
|
||||
build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict }
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown field") {
|
||||
t.Errorf("want unknown field error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToolchainsUnknownClass(t *testing.T) {
|
||||
src := `
|
||||
toolchains {
|
||||
gcc { platforms = "linux" class = bogus }
|
||||
}
|
||||
build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict }
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown compiler class") {
|
||||
t.Errorf("want unknown class error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToolchainsClassAndBinary(t *testing.T) {
|
||||
src := `
|
||||
toolchains {
|
||||
gcc13 { platforms = "linux" binary = "gcc-13" class = gnu }
|
||||
msvc { platforms = "windows" class = msvc }
|
||||
}
|
||||
build_defaults { language="c" standard="c11" sources="x.c" output="s" warnings=strict }
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if len(f.Toolchains) != 2 {
|
||||
t.Fatalf("toolchains = %d", len(f.Toolchains))
|
||||
}
|
||||
if f.Toolchains[0].Binary != "gcc-13" || f.Toolchains[0].Class != "gnu" {
|
||||
t.Errorf("gcc13 = %+v", f.Toolchains[0])
|
||||
}
|
||||
if f.Toolchains[1].Class != "msvc" {
|
||||
t.Errorf("msvc class = %q", f.Toolchains[1].Class)
|
||||
}
|
||||
}
|
||||
74
dsl/build_string_test.go
Normal file
74
dsl/build_string_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package dsl
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBuildProfileString(t *testing.T) {
|
||||
cases := []struct {
|
||||
p BuildProfile
|
||||
want string
|
||||
}{
|
||||
{ProfileRelease, "release"},
|
||||
{ProfileDebug, "debug"},
|
||||
{ProfileSanitized, "sanitized"},
|
||||
{ProfileUnset, "unset"},
|
||||
{BuildProfile(999), "unset"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := c.p.String(); got != c.want {
|
||||
t.Errorf("BuildProfile(%d).String() = %q, want %q", c.p, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWarningLevelString(t *testing.T) {
|
||||
cases := []struct {
|
||||
w WarningLevel
|
||||
want string
|
||||
}{
|
||||
{WarningsDefault, "default"},
|
||||
{WarningsStrict, "strict"},
|
||||
{WarningsPedantic, "pedantic"},
|
||||
{WarningsUnset, "unset"},
|
||||
{WarningLevel(999), "unset"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := c.w.String(); got != c.want {
|
||||
t.Errorf("WarningLevel(%d).String() = %q, want %q", c.w, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeFromWrapperAndDefines(t *testing.T) {
|
||||
dst := &BuildConfig{}
|
||||
src := &BuildConfig{
|
||||
Wrapper: "valgrind",
|
||||
Defines: map[string]string{"DEBUG": "1", "VERSION": "2"},
|
||||
}
|
||||
dst.MergeFrom(src)
|
||||
if dst.Wrapper != "valgrind" {
|
||||
t.Errorf("Wrapper = %q, want valgrind", dst.Wrapper)
|
||||
}
|
||||
if dst.Defines["DEBUG"] != "1" || dst.Defines["VERSION"] != "2" {
|
||||
t.Errorf("Defines = %v", dst.Defines)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeFromDefinesIntoExisting(t *testing.T) {
|
||||
dst := &BuildConfig{Defines: map[string]string{"KEEP": "old"}}
|
||||
src := &BuildConfig{Defines: map[string]string{"NEW": "1", "KEEP": "new"}}
|
||||
dst.MergeFrom(src)
|
||||
if dst.Defines["KEEP"] != "new" {
|
||||
t.Errorf("KEEP = %q, want new (overridden)", dst.Defines["KEEP"])
|
||||
}
|
||||
if dst.Defines["NEW"] != "1" {
|
||||
t.Errorf("NEW = %q, want 1", dst.Defines["NEW"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeFromNilSrc(t *testing.T) {
|
||||
dst := &BuildConfig{Language: "c"}
|
||||
dst.MergeFrom(nil)
|
||||
if dst.Language != "c" {
|
||||
t.Errorf("dst mutated on nil merge: %+v", dst)
|
||||
}
|
||||
}
|
||||
175
dsl/lexer_test.go
Normal file
175
dsl/lexer_test.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package dsl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTokenStringAndUnknownType(t *testing.T) {
|
||||
tok := Token{Type: TOKEN_IDENT, Value: "foo", Line: 2, Col: 5}
|
||||
s := tok.String()
|
||||
if !strings.Contains(s, "IDENT") || !strings.Contains(s, "foo") {
|
||||
t.Errorf("Token.String() = %q", s)
|
||||
}
|
||||
if got := TokenType(999).String(); got != "UNKNOWN" {
|
||||
t.Errorf("TokenType(999).String() = %q, want UNKNOWN", got)
|
||||
}
|
||||
for tt, want := range map[TokenType]string{
|
||||
TOKEN_STRING: "STRING",
|
||||
TOKEN_FLOAT: "FLOAT",
|
||||
TOKEN_INT: "INT",
|
||||
TOKEN_DURATION: "DURATION",
|
||||
TOKEN_SIZE: "SIZE",
|
||||
TOKEN_LBRACE: "{",
|
||||
TOKEN_RBRACE: "}",
|
||||
TOKEN_LPAREN: "(",
|
||||
TOKEN_RPAREN: ")",
|
||||
TOKEN_ASSIGN: "=",
|
||||
TOKEN_TILDE: "~",
|
||||
TOKEN_EOF: "EOF",
|
||||
} {
|
||||
if got := tt.String(); got != want {
|
||||
t.Errorf("TokenType(%d) = %q, want %q", tt, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerLineComment(t *testing.T) {
|
||||
src := `
|
||||
// leading comment
|
||||
build "make" // trailing
|
||||
group("g") { // inside
|
||||
weight = 1.0
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
`
|
||||
if _, _, err := Parse(src); err != nil {
|
||||
t.Errorf("parse with comments: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerUnterminatedString(t *testing.T) {
|
||||
_, _, err := Parse(`build "unterminated`)
|
||||
if err == nil || !strings.Contains(err.Error(), "unterminated") {
|
||||
t.Errorf("want unterminated string error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerUnknownEscape(t *testing.T) {
|
||||
_, _, err := Parse(`build "bad\zescape"`)
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown escape") {
|
||||
t.Errorf("want unknown escape error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerEscapeSequences(t *testing.T) {
|
||||
src := `build "a\nb\tc\\d\"e"`
|
||||
f, _, err := Parse(src + "\ngroup(\"g\") { weight = 1.0 test(\"t\") { stdout = \"\" } }")
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if f.Build != "a\nb\tc\\d\"e" {
|
||||
t.Errorf("escape sequences = %q", f.Build)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerUnexpectedCharacter(t *testing.T) {
|
||||
_, _, err := Parse("build @invalid")
|
||||
if err == nil || !strings.Contains(err.Error(), "unexpected character") {
|
||||
t.Errorf("want unexpected character error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerUnterminatedHeredoc(t *testing.T) {
|
||||
src := "build \"x\"\ngroup(\"g\") { weight = 1.0 test(\"t\") { stdin = \"\"\"\nnever closed\n } }"
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "unterminated heredoc") {
|
||||
t.Errorf("want unterminated heredoc, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerSizeSuffixes(t *testing.T) {
|
||||
cases := map[string]int64{
|
||||
"1024": 1024,
|
||||
"1B": 1,
|
||||
"1K": 1024,
|
||||
"1KB": 1024,
|
||||
"1KiB": 1024,
|
||||
"1M": 1024 * 1024,
|
||||
"1MB": 1024 * 1024,
|
||||
"1MiB": 1024 * 1024,
|
||||
"1G": 1024 * 1024 * 1024,
|
||||
"1GB": 1024 * 1024 * 1024,
|
||||
"1GiB": 1024 * 1024 * 1024,
|
||||
}
|
||||
for literal, want := range cases {
|
||||
src := "build \"x\"\nmemory_limit = " + literal + "\ngroup(\"g\") { weight = 1.0 test(\"t\") { stdout = \"\" } }"
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Errorf("parse %q: %v", literal, err)
|
||||
continue
|
||||
}
|
||||
if f.MemoryLimit != want {
|
||||
t.Errorf("memory_limit %s = %d, want %d", literal, f.MemoryLimit, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerDurationSuffixes(t *testing.T) {
|
||||
cases := []string{"5ms", "10s", "2m"}
|
||||
for _, d := range cases {
|
||||
src := "build \"x\"\ntimeout " + d + "\ngroup(\"g\") { weight = 1.0 test(\"t\") { stdout = \"\" } }"
|
||||
if _, _, err := Parse(src); err != nil {
|
||||
t.Errorf("parse timeout %s: %v", d, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerNegativeInt(t *testing.T) {
|
||||
src := `
|
||||
build "x"
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
test("t") {
|
||||
exitCode = -1
|
||||
stdout = ""
|
||||
}
|
||||
}
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
tst := f.Groups[0].Tests[0]
|
||||
if tst.ExitCode == nil || *tst.ExitCode != -1 {
|
||||
t.Errorf("ExitCode = %v, want -1", tst.ExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerFloatNumber(t *testing.T) {
|
||||
src := `
|
||||
build "x"
|
||||
group("g") {
|
||||
weight = 0.75
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
group("g2") {
|
||||
weight = 0.25
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if f.Groups[0].Weight != 0.75 {
|
||||
t.Errorf("weight = %v", f.Groups[0].Weight)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLexerInvalidSizeUnit(t *testing.T) {
|
||||
_, _, err := Parse("build \"x\"\nmemory_limit = 10Z\n")
|
||||
if err == nil {
|
||||
t.Error("expected error for invalid size unit")
|
||||
}
|
||||
}
|
||||
187
dsl/matcher_test.go
Normal file
187
dsl/matcher_test.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package dsl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExactMatcherPass(t *testing.T) {
|
||||
m := ExactMatcher{Value: "hello\n"}
|
||||
if errs := m.Match("stdout", "hello\n"); errs != nil {
|
||||
t.Errorf("expected pass, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExactMatcherMismatch(t *testing.T) {
|
||||
m := ExactMatcher{Value: "hello\n"}
|
||||
errs := m.Match("stdout", "world\n")
|
||||
if len(errs) != 1 {
|
||||
t.Fatalf("want 1 error, got %d: %v", len(errs), errs)
|
||||
}
|
||||
if !strings.Contains(errs[0], "stdout mismatch") {
|
||||
t.Errorf("error missing label: %q", errs[0])
|
||||
}
|
||||
if !strings.Contains(errs[0], "hello") || !strings.Contains(errs[0], "world") {
|
||||
t.Errorf("error missing values: %q", errs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsMatcherPass(t *testing.T) {
|
||||
m := ContainsMatcher{Substr: "needle"}
|
||||
if errs := m.Match("stdout", "haystack with a needle inside"); errs != nil {
|
||||
t.Errorf("expected pass, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainsMatcherMissing(t *testing.T) {
|
||||
m := ContainsMatcher{Substr: "needle"}
|
||||
errs := m.Match("stdout", "only hay here")
|
||||
if len(errs) != 1 {
|
||||
t.Fatalf("want 1 error, got %d: %v", len(errs), errs)
|
||||
}
|
||||
if !strings.Contains(errs[0], "needle") {
|
||||
t.Errorf("error missing substring: %q", errs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexMatcherPass(t *testing.T) {
|
||||
m := RegexMatcher{Pattern: `^hello .*!$`}
|
||||
if errs := m.Match("stdout", "hello world!"); errs != nil {
|
||||
t.Errorf("expected pass, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexMatcherMismatch(t *testing.T) {
|
||||
m := RegexMatcher{Pattern: `^\d+$`}
|
||||
errs := m.Match("stdout", "not-a-number")
|
||||
if len(errs) != 1 {
|
||||
t.Fatalf("want 1 error, got %d: %v", len(errs), errs)
|
||||
}
|
||||
if !strings.Contains(errs[0], "does not match") {
|
||||
t.Errorf("error unexpected: %q", errs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexMatcherInvalidPattern(t *testing.T) {
|
||||
m := RegexMatcher{Pattern: `[`}
|
||||
errs := m.Match("stdout", "anything")
|
||||
if len(errs) != 1 {
|
||||
t.Fatalf("want 1 error, got %d: %v", len(errs), errs)
|
||||
}
|
||||
if !strings.Contains(errs[0], "invalid regex") {
|
||||
t.Errorf("error unexpected: %q", errs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericEpsMatcherPassWithinEps(t *testing.T) {
|
||||
m := NumericEpsMatcher{Epsilon: 0.01, Value: "1.0 2.0 3.0"}
|
||||
if errs := m.Match("stdout", "1.005 1.999 3.0"); errs != nil {
|
||||
t.Errorf("expected pass, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericEpsMatcherExceedsEps(t *testing.T) {
|
||||
m := NumericEpsMatcher{Epsilon: 0.01, Value: "1.0 2.0"}
|
||||
errs := m.Match("stdout", "1.0 2.5")
|
||||
if len(errs) != 1 {
|
||||
t.Fatalf("want 1 error, got %d: %v", len(errs), errs)
|
||||
}
|
||||
if !strings.Contains(errs[0], "number[1]") {
|
||||
t.Errorf("error missing index: %q", errs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericEpsMatcherCountMismatch(t *testing.T) {
|
||||
m := NumericEpsMatcher{Epsilon: 0.01, Value: "1 2 3"}
|
||||
errs := m.Match("stdout", "1 2")
|
||||
if len(errs) != 1 {
|
||||
t.Fatalf("want 1 error, got %d: %v", len(errs), errs)
|
||||
}
|
||||
if !strings.Contains(errs[0], "expected 3 numbers, got 2") {
|
||||
t.Errorf("error unexpected: %q", errs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericEpsMatcherBadExpected(t *testing.T) {
|
||||
m := NumericEpsMatcher{Epsilon: 0.01, Value: "1 foo"}
|
||||
errs := m.Match("stdout", "1 2")
|
||||
if len(errs) != 1 || !strings.Contains(errs[0], "cannot parse expected") {
|
||||
t.Errorf("want expected-parse error, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericEpsMatcherBadActual(t *testing.T) {
|
||||
m := NumericEpsMatcher{Epsilon: 0.01, Value: "1 2"}
|
||||
errs := m.Match("stdout", "1 bar")
|
||||
if len(errs) != 1 || !strings.Contains(errs[0], "cannot parse actual") {
|
||||
t.Errorf("want actual-parse error, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumericEpsMatcherMultipleErrors(t *testing.T) {
|
||||
m := NumericEpsMatcher{Epsilon: 0.01, Value: "1 2 3"}
|
||||
errs := m.Match("stdout", "9 8 7")
|
||||
if len(errs) != 3 {
|
||||
t.Errorf("want 3 errors, got %d: %v", len(errs), errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyOrderMatcherPassReordered(t *testing.T) {
|
||||
m := AnyOrderMatcher{Lines: []string{"a", "b", "c"}}
|
||||
if errs := m.Match("stdout", "c\nb\na\n"); errs != nil {
|
||||
t.Errorf("expected pass, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyOrderMatcherPassNoTrailingNewline(t *testing.T) {
|
||||
m := AnyOrderMatcher{Lines: []string{"x", "y"}}
|
||||
if errs := m.Match("stdout", "y\nx"); errs != nil {
|
||||
t.Errorf("expected pass, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyOrderMatcherCountMismatch(t *testing.T) {
|
||||
m := AnyOrderMatcher{Lines: []string{"a", "b"}}
|
||||
errs := m.Match("stdout", "a\nb\nc\n")
|
||||
if len(errs) != 1 || !strings.Contains(errs[0], "expected 2 lines, got 3") {
|
||||
t.Errorf("want count error, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyOrderMatcherLineMismatch(t *testing.T) {
|
||||
m := AnyOrderMatcher{Lines: []string{"a", "b"}}
|
||||
errs := m.Match("stdout", "a\nz\n")
|
||||
if len(errs) != 1 || !strings.Contains(errs[0], "line mismatch") {
|
||||
t.Errorf("want line mismatch, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyOrderMatcherEmptyBoth(t *testing.T) {
|
||||
m := AnyOrderMatcher{Lines: []string{}}
|
||||
if errs := m.Match("stdout", ""); errs != nil {
|
||||
t.Errorf("expected pass on empty, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoMatcherAlwaysPasses(t *testing.T) {
|
||||
m := NoMatcher{}
|
||||
if errs := m.Match("stdout", "anything at all\n"); errs != nil {
|
||||
t.Errorf("NoMatcher should never fail, got %v", errs)
|
||||
}
|
||||
if errs := m.Match("stderr", ""); errs != nil {
|
||||
t.Errorf("NoMatcher should never fail on empty, got %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitLinesEmpty(t *testing.T) {
|
||||
if got := splitLines(""); len(got) != 0 {
|
||||
t.Errorf("splitLines(\"\") = %v, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitLinesTrailingNewline(t *testing.T) {
|
||||
got := splitLines("a\nb\n")
|
||||
if len(got) != 2 || got[0] != "a" || got[1] != "b" {
|
||||
t.Errorf("splitLines trailing = %v, want [a b]", got)
|
||||
}
|
||||
}
|
||||
220
dsl/merge_test.go
Normal file
220
dsl/merge_test.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package dsl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMergeLegacyBuildFields(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTempJdg(t, dir, "common.jdg", `
|
||||
build "make"
|
||||
build_linux "make linux"
|
||||
build_windows "make windows"
|
||||
build_darwin "make darwin"
|
||||
timeout 7s
|
||||
memory_limit = 256MB
|
||||
`)
|
||||
mainPath := writeTempJdg(t, dir, "main.jdg", `
|
||||
include "common.jdg"
|
||||
group("g") { weight = 1.0 test("t") { stdout = "ok\n" } }
|
||||
`)
|
||||
f, _, err := ParseFile(mainPath)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if f.Build != "make" || f.BuildLinux != "make linux" ||
|
||||
f.BuildWindows != "make windows" || f.BuildDarwin != "make darwin" {
|
||||
t.Errorf("legacy build fields not merged: %+v", f)
|
||||
}
|
||||
if f.Timeout != 7*time.Second {
|
||||
t.Errorf("Timeout = %v, want 7s", f.Timeout)
|
||||
}
|
||||
if f.MemoryLimit != 256*1024*1024 {
|
||||
t.Errorf("MemoryLimit = %d", f.MemoryLimit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeDuplicateToolchainFromInclude(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTempJdg(t, dir, "common.jdg", `
|
||||
toolchains {
|
||||
gcc { platforms = "linux" }
|
||||
}
|
||||
`)
|
||||
mainPath := writeTempJdg(t, dir, "main.jdg", `
|
||||
toolchains {
|
||||
gcc { platforms = "linux" }
|
||||
}
|
||||
include "common.jdg"
|
||||
build "make"
|
||||
group("g") { weight = 1.0 test("t") { stdout = "ok\n" } }
|
||||
`)
|
||||
_, _, err := ParseFile(mainPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected duplicate toolchain error from merge")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "duplicate toolchain") {
|
||||
t.Errorf("error %q does not mention duplicate toolchain", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeDuplicateBuildErrors(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTempJdg(t, dir, "common.jdg", `
|
||||
build "release" { profile = release }
|
||||
`)
|
||||
mainPath := writeTempJdg(t, dir, "main.jdg", `
|
||||
build_defaults {
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources = "*.c"
|
||||
output = "solution"
|
||||
warnings = strict
|
||||
}
|
||||
build "release" { profile = debug }
|
||||
include "common.jdg"
|
||||
group("g") { weight = 1.0 test("t") { stdout = "ok\n" } }
|
||||
`)
|
||||
_, _, err := ParseFile(mainPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected duplicate build error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "duplicate build") {
|
||||
t.Errorf("error %q does not mention duplicate build", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeDuplicateGroupErrors(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTempJdg(t, dir, "common.jdg", `
|
||||
group("shared") { weight = 0.5 test("t") { stdout = "ok\n" } }
|
||||
`)
|
||||
mainPath := writeTempJdg(t, dir, "main.jdg", `
|
||||
build "make"
|
||||
group("shared") { weight = 0.5 test("t2") { stdout = "ok\n" } }
|
||||
include "common.jdg"
|
||||
`)
|
||||
_, _, err := ParseFile(mainPath)
|
||||
if err == nil {
|
||||
t.Fatal("expected duplicate group error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "duplicate group") {
|
||||
t.Errorf("error %q does not mention duplicate group", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeGroupsAppended(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTempJdg(t, dir, "common.jdg", `
|
||||
build "make"
|
||||
group("included") { weight = 0.5 test("t") { stdout = "ok\n" } }
|
||||
`)
|
||||
mainPath := writeTempJdg(t, dir, "main.jdg", `
|
||||
include "common.jdg"
|
||||
group("local") { weight = 0.5 test("t") { stdout = "ok\n" } }
|
||||
`)
|
||||
f, _, err := ParseFile(mainPath)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if len(f.Groups) != 2 {
|
||||
t.Fatalf("want 2 groups, got %d", len(f.Groups))
|
||||
}
|
||||
names := []string{f.Groups[0].Name, f.Groups[1].Name}
|
||||
if !(contains(names, "included") && contains(names, "local")) {
|
||||
t.Errorf("groups = %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeBuildsAppended(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTempJdg(t, dir, "common.jdg", `
|
||||
build_defaults {
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources = "*.c"
|
||||
output = "solution"
|
||||
warnings = strict
|
||||
}
|
||||
build "release" { profile = release }
|
||||
`)
|
||||
mainPath := writeTempJdg(t, dir, "main.jdg", `
|
||||
include "common.jdg"
|
||||
build "debug" { profile = debug }
|
||||
group("g") { weight = 1.0 test("t") { stdout = "ok\n" } }
|
||||
`)
|
||||
f, _, err := ParseFile(mainPath)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if len(f.Builds) != 2 {
|
||||
t.Fatalf("want 2 builds, got %d", len(f.Builds))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeBinarySourcesAndFlags(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTempJdg(t, dir, "common.jdg", `
|
||||
build "make"
|
||||
binary = "solution"
|
||||
sources = "*.c"
|
||||
normalize_crlf = true
|
||||
trim_trailing_ws = true
|
||||
`)
|
||||
mainPath := writeTempJdg(t, dir, "main.jdg", `
|
||||
include "common.jdg"
|
||||
group("g") { weight = 1.0 test("t") { stdout = "ok\n" } }
|
||||
`)
|
||||
f, _, err := ParseFile(mainPath)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if f.Binary != "solution" {
|
||||
t.Errorf("Binary = %q, want solution", f.Binary)
|
||||
}
|
||||
if f.Sources != "*.c" {
|
||||
t.Errorf("Sources = %q, want *.c", f.Sources)
|
||||
}
|
||||
if !f.NormalizeCRLF {
|
||||
t.Error("NormalizeCRLF not merged")
|
||||
}
|
||||
if !f.TrimTrailingWS {
|
||||
t.Error("TrimTrailingWS not merged")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeLocalOverridesTimeout(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTempJdg(t, dir, "common.jdg", `
|
||||
build "make"
|
||||
timeout 10s
|
||||
memory_limit = 128MB
|
||||
`)
|
||||
mainPath := writeTempJdg(t, dir, "main.jdg", `
|
||||
include "common.jdg"
|
||||
timeout 3s
|
||||
memory_limit = 64MB
|
||||
group("g") { weight = 1.0 test("t") { stdout = "ok\n" } }
|
||||
`)
|
||||
f, _, err := ParseFile(mainPath)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if f.Timeout != 3*time.Second {
|
||||
t.Errorf("Timeout = %v, want local 3s", f.Timeout)
|
||||
}
|
||||
if f.MemoryLimit != 64*1024*1024 {
|
||||
t.Errorf("MemoryLimit = %d, want local 64MB", f.MemoryLimit)
|
||||
}
|
||||
}
|
||||
|
||||
func contains(xs []string, s string) bool {
|
||||
for _, x := range xs {
|
||||
if x == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
299
dsl/parser_errors_test.go
Normal file
299
dsl/parser_errors_test.go
Normal file
@@ -0,0 +1,299 @@
|
||||
package dsl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParserErrorCases(t *testing.T) {
|
||||
const prefix = `build "go build ."` + "\n"
|
||||
cases := []struct {
|
||||
name string
|
||||
body string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "group missing lparen",
|
||||
body: `group "g" { weight = 1.0 test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group name not string",
|
||||
body: `group(foo) { weight = 1.0 test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group missing rparen",
|
||||
body: `group("g" { weight = 1.0 test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group missing lbrace",
|
||||
body: `group("g") weight = 1.0`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group non-ident in body",
|
||||
body: `group("g") { 42 }`,
|
||||
want: "unexpected token",
|
||||
},
|
||||
{
|
||||
name: "group weight missing assign",
|
||||
body: `group("g") { weight 1.0 test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group weight non-number",
|
||||
body: `group("g") { weight = "bad" test("t") { stdout = "" } }`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "group timeout missing assign",
|
||||
body: `group("g") { weight = 1.0 timeout 5s test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group timeout non-duration",
|
||||
body: `group("g") { weight = 1.0 timeout = "5s" test("t") { stdout = "" } }`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "group memory_limit missing assign",
|
||||
body: `group("g") { weight = 1.0 memory_limit 64MB test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group memory_limit non-size",
|
||||
body: `group("g") { weight = 1.0 memory_limit = "64MB" test("t") { stdout = "" } }`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "group scoring missing assign",
|
||||
body: `group("g") { weight = 1.0 scoring partial test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group scoring non-ident",
|
||||
body: `group("g") { weight = 1.0 scoring = "partial" test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group env missing lparen",
|
||||
body: `group("g") { weight = 1.0 env "K" = "v" test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group env key not string",
|
||||
body: `group("g") { weight = 1.0 env(K) = "v" test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group env missing rparen",
|
||||
body: `group("g") { weight = 1.0 env("K" = "v" test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group env missing assign",
|
||||
body: `group("g") { weight = 1.0 env("K") "v" test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group env value not string",
|
||||
body: `group("g") { weight = 1.0 env("K") = K test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group wrapper missing assign",
|
||||
body: `group("g") { weight = 1.0 wrapper "x" test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group wrapper not string",
|
||||
body: `group("g") { weight = 1.0 wrapper = X test("t") { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "group unclosed",
|
||||
body: `group("g") { weight = 1.0 test("t") { stdout = "" }`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "group pattern error",
|
||||
body: `group("g") { weight = 1.0 pattern { bogus = 1 } }`,
|
||||
want: "",
|
||||
},
|
||||
|
||||
{
|
||||
name: "test missing lparen",
|
||||
body: `group("g") { weight = 1.0 test "t" { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test name not string",
|
||||
body: `group("g") { weight = 1.0 test(t) { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test missing rparen",
|
||||
body: `group("g") { weight = 1.0 test("t" { stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test missing lbrace",
|
||||
body: `group("g") { weight = 1.0 test("t") stdout = "" }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test non-ident in body",
|
||||
body: `group("g") { weight = 1.0 test("t") { 42 } }`,
|
||||
want: "unexpected token",
|
||||
},
|
||||
{
|
||||
name: "test stdin missing assign",
|
||||
body: `group("g") { weight = 1.0 test("t") { stdin "x" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test stdin not string",
|
||||
body: `group("g") { weight = 1.0 test("t") { stdin = 1 stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test args missing assign",
|
||||
body: `group("g") { weight = 1.0 test("t") { args "x" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test exitCode missing assign",
|
||||
body: `group("g") { weight = 1.0 test("t") { exitCode 0 stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test exitCode not int",
|
||||
body: `group("g") { weight = 1.0 test("t") { exitCode = "x" stdout = "" } }`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "test timeout missing assign",
|
||||
body: `group("g") { weight = 1.0 test("t") { timeout 2s stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test timeout not duration",
|
||||
body: `group("g") { weight = 1.0 test("t") { timeout = "2s" stdout = "" } }`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "test memory_limit missing assign",
|
||||
body: `group("g") { weight = 1.0 test("t") { memory_limit 64MB stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test memory_limit not size",
|
||||
body: `group("g") { weight = 1.0 test("t") { memory_limit = "64MB" stdout = "" } }`,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "test wrapper missing assign",
|
||||
body: `group("g") { weight = 1.0 test("t") { wrapper "x" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test wrapper not string",
|
||||
body: `group("g") { weight = 1.0 test("t") { wrapper = X stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test env missing lparen",
|
||||
body: `group("g") { weight = 1.0 test("t") { env "K" = "v" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test env key not string",
|
||||
body: `group("g") { weight = 1.0 test("t") { env(K) = "v" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test env missing rparen",
|
||||
body: `group("g") { weight = 1.0 test("t") { env("K" = "v" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test env missing assign",
|
||||
body: `group("g") { weight = 1.0 test("t") { env("K") "v" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test env value not string",
|
||||
body: `group("g") { weight = 1.0 test("t") { env("K") = K stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test file missing lparen",
|
||||
body: `group("g") { weight = 1.0 test("t") { file "x" = "y" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test file key not string",
|
||||
body: `group("g") { weight = 1.0 test("t") { file(x) = "y" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test file missing rparen",
|
||||
body: `group("g") { weight = 1.0 test("t") { file("x" = "y" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test file missing assign",
|
||||
body: `group("g") { weight = 1.0 test("t") { file("x") "y" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test file value not string",
|
||||
body: `group("g") { weight = 1.0 test("t") { file("x") = y stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test outFile missing lparen",
|
||||
body: `group("g") { weight = 1.0 test("t") { outFile "x" = "y" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test outFile key not string",
|
||||
body: `group("g") { weight = 1.0 test("t") { outFile(x) = "y" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test outFile missing rparen",
|
||||
body: `group("g") { weight = 1.0 test("t") { outFile("x" = "y" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test outFile missing assign",
|
||||
body: `group("g") { weight = 1.0 test("t") { outFile("x") "y" stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test outFile value not string",
|
||||
body: `group("g") { weight = 1.0 test("t") { outFile("x") = y stdout = "" } }`,
|
||||
want: "expected",
|
||||
},
|
||||
{
|
||||
name: "test unclosed",
|
||||
body: `group("g") { weight = 1.0 test("t") { stdout = ""`,
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
_, _, err := Parse(prefix + c.body)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
if c.want != "" && !strings.Contains(err.Error(), c.want) {
|
||||
t.Errorf("error = %q, want substring %q", err.Error(), c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
237
dsl/parser_features_test.go
Normal file
237
dsl/parser_features_test.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package dsl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func parseOrFatal(t *testing.T, src string) *File {
|
||||
t.Helper()
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse error: %v", err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func parseExpectError(t *testing.T, src, wantSubstr string) {
|
||||
t.Helper()
|
||||
_, _, err := Parse(src)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error containing %q, got nil", wantSubstr)
|
||||
}
|
||||
if !strings.Contains(err.Error(), wantSubstr) {
|
||||
t.Errorf("error = %q, want substring %q", err.Error(), wantSubstr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTestFileAndOutFile(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
test("files") {
|
||||
file("input.txt") = "1 2 3\n"
|
||||
outFile("result.txt") = "6\n"
|
||||
stdout = ""
|
||||
}
|
||||
}
|
||||
`
|
||||
f := parseOrFatal(t, src)
|
||||
tst := f.Groups[0].Tests[0]
|
||||
if tst.InFiles["input.txt"] != "1 2 3\n" {
|
||||
t.Errorf("InFiles[input.txt] = %q", tst.InFiles["input.txt"])
|
||||
}
|
||||
if tst.OutFiles["result.txt"] != "6\n" {
|
||||
t.Errorf("OutFiles[result.txt] = %q", tst.OutFiles["result.txt"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTestEnvAndWrapper(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
test("env") {
|
||||
env("FOO") = "bar"
|
||||
env("BAZ") = "qux"
|
||||
wrapper = "valgrind"
|
||||
stdout = ""
|
||||
}
|
||||
}
|
||||
`
|
||||
f := parseOrFatal(t, src)
|
||||
tst := f.Groups[0].Tests[0]
|
||||
if tst.Env["FOO"] != "bar" || tst.Env["BAZ"] != "qux" {
|
||||
t.Errorf("Env = %v", tst.Env)
|
||||
}
|
||||
if tst.Wrapper != "valgrind" {
|
||||
t.Errorf("Wrapper = %q", tst.Wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTestTimeoutAndMemoryOverride(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
timeout 10s
|
||||
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
memory_limit = 256MB
|
||||
test("override") {
|
||||
timeout = 2s
|
||||
memory_limit = 64MB
|
||||
stdout = ""
|
||||
}
|
||||
}
|
||||
`
|
||||
f := parseOrFatal(t, src)
|
||||
tst := f.Groups[0].Tests[0]
|
||||
if tst.Timeout != 2*time.Second {
|
||||
t.Errorf("Timeout = %v, want 2s", tst.Timeout)
|
||||
}
|
||||
if tst.MemoryLimit != 64*1024*1024 {
|
||||
t.Errorf("MemoryLimit = %d, want %d", tst.MemoryLimit, 64*1024*1024)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTestExitCodeNonZero(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
test("fail") {
|
||||
exitCode = 42
|
||||
stdout = ""
|
||||
}
|
||||
}
|
||||
`
|
||||
f := parseOrFatal(t, src)
|
||||
tst := f.Groups[0].Tests[0]
|
||||
if tst.ExitCode == nil || *tst.ExitCode != 42 {
|
||||
t.Errorf("ExitCode = %v, want 42", tst.ExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTestUnknownKeyword(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
test("bad") {
|
||||
bogus = "x"
|
||||
}
|
||||
}
|
||||
`
|
||||
parseExpectError(t, src, `"bogus"`)
|
||||
}
|
||||
|
||||
func TestParseGroupScoringPartial(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
scoring = partial
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
`
|
||||
f := parseOrFatal(t, src)
|
||||
if f.Groups[0].Scoring != ScoringPartial {
|
||||
t.Errorf("Scoring = %v, want partial", f.Groups[0].Scoring)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGroupScoringAllOrNone(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
scoring = all_or_none
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
`
|
||||
f := parseOrFatal(t, src)
|
||||
if f.Groups[0].Scoring != ScoringAllOrNone {
|
||||
t.Errorf("Scoring = %v, want all_or_none", f.Groups[0].Scoring)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGroupScoringUnknown(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
scoring = magic
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
`
|
||||
parseExpectError(t, src, "unknown scoring mode")
|
||||
}
|
||||
|
||||
func TestParseGroupEnvAndWrapper(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
env("LANG") = "C"
|
||||
env("DEBUG") = "1"
|
||||
wrapper = "strace"
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
`
|
||||
f := parseOrFatal(t, src)
|
||||
g := f.Groups[0]
|
||||
if g.Env["LANG"] != "C" || g.Env["DEBUG"] != "1" {
|
||||
t.Errorf("group Env = %v", g.Env)
|
||||
}
|
||||
if g.Wrapper != "strace" {
|
||||
t.Errorf("group Wrapper = %q", g.Wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGroupMemoryLimit(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
memory_limit = 128MB
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
`
|
||||
f := parseOrFatal(t, src)
|
||||
if f.Groups[0].MemoryLimit != 128*1024*1024 {
|
||||
t.Errorf("group MemoryLimit = %d", f.Groups[0].MemoryLimit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGroupUnknownKeyword(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
foobar = 1
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
`
|
||||
parseExpectError(t, src, `"foobar"`)
|
||||
}
|
||||
|
||||
func TestParseGroupMissingWeight(t *testing.T) {
|
||||
src := `
|
||||
build "go build ."
|
||||
group("g") {
|
||||
test("t") { stdout = "" }
|
||||
}
|
||||
`
|
||||
_, warns, err := Parse(src)
|
||||
if err != nil && !strings.Contains(err.Error(), "weight") {
|
||||
return
|
||||
}
|
||||
if err == nil && len(warns) == 0 {
|
||||
t.Error("expected error or warning about missing weight")
|
||||
}
|
||||
}
|
||||
193
dsl/parser_misc_test.go
Normal file
193
dsl/parser_misc_test.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package dsl
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParsePatternDirsMode(t *testing.T) {
|
||||
src := `
|
||||
build "make"
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
pattern {
|
||||
dirs = "tests/*"
|
||||
input = "in.txt"
|
||||
output = "out.txt"
|
||||
args = "--case" "{name}"
|
||||
}
|
||||
}
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
pat := f.Groups[0].Pattern
|
||||
if pat == nil {
|
||||
t.Fatal("no pattern")
|
||||
}
|
||||
if pat.DirsGlob != "tests/*" {
|
||||
t.Errorf("DirsGlob = %q", pat.DirsGlob)
|
||||
}
|
||||
if pat.InputFile != "in.txt" || pat.OutputFile != "out.txt" {
|
||||
t.Errorf("InputFile/OutputFile = %q/%q", pat.InputFile, pat.OutputFile)
|
||||
}
|
||||
if len(pat.Args) != 2 || pat.Args[0] != "--case" || pat.Args[1] != "{name}" {
|
||||
t.Errorf("Args = %v", pat.Args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePatternUnknownField(t *testing.T) {
|
||||
src := `
|
||||
build "make"
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
pattern { bogus = "x" }
|
||||
}
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown pattern field") {
|
||||
t.Errorf("want unknown pattern field error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePatternNonIdent(t *testing.T) {
|
||||
src := `
|
||||
build "make"
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
pattern { "x" = "y" }
|
||||
}
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil {
|
||||
t.Error("expected error on non-ident in pattern")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNormalizeCRLFAndTrimWS(t *testing.T) {
|
||||
src := `
|
||||
build "make"
|
||||
normalize_crlf = true
|
||||
trim_trailing_ws = false
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if !f.NormalizeCRLF {
|
||||
t.Error("NormalizeCRLF should be true")
|
||||
}
|
||||
if f.TrimTrailingWS {
|
||||
t.Error("TrimTrailingWS should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBinaryAndSources(t *testing.T) {
|
||||
src := `
|
||||
build "make"
|
||||
binary = "sol"
|
||||
sources = "main.c"
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if f.Binary != "sol" {
|
||||
t.Errorf("Binary = %q", f.Binary)
|
||||
}
|
||||
if f.Sources != "main.c" {
|
||||
t.Errorf("Sources = %q", f.Sources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMemoryLimitBareInt(t *testing.T) {
|
||||
src := `
|
||||
build "make"
|
||||
memory_limit = 1024
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
f, _, err := Parse(src)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
if f.MemoryLimit != 1024 {
|
||||
t.Errorf("MemoryLimit = %d, want 1024", f.MemoryLimit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBoolInvalidIdent(t *testing.T) {
|
||||
_, _, err := Parse("build \"make\"\nnormalize_crlf = maybe\n")
|
||||
if err == nil || !strings.Contains(err.Error(), "true/false") {
|
||||
t.Errorf("want true/false error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBoolNonIdent(t *testing.T) {
|
||||
_, _, err := Parse("build \"make\"\nnormalize_crlf = \"true\"\n")
|
||||
if err == nil || !strings.Contains(err.Error(), "true/false") {
|
||||
t.Errorf("want true/false error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTopLevelNonIdent(t *testing.T) {
|
||||
_, _, err := Parse(`"stray"`)
|
||||
if err == nil || !strings.Contains(err.Error(), "unexpected token") {
|
||||
t.Errorf("want unexpected token error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMatcherOrAssignNoMatcher(t *testing.T) {
|
||||
src := `
|
||||
build "make"
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
test("t") { stdout 42 }
|
||||
}
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "expected matcher") {
|
||||
t.Errorf("want matcher error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseStringListEmpty(t *testing.T) {
|
||||
src := `
|
||||
build "make"
|
||||
group("g") {
|
||||
weight = 1.0
|
||||
test("t") { args = stdout = "" }
|
||||
}
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil {
|
||||
t.Error("expected error on empty string list")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFileMissing(t *testing.T) {
|
||||
_, _, err := ParseFile("/nonexistent/judge-test-does-not-exist.jdg")
|
||||
if err == nil {
|
||||
t.Error("expected error on missing file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateBuildsMixedLegacyAndStructured(t *testing.T) {
|
||||
src := `
|
||||
build "legacy shell"
|
||||
build_defaults {
|
||||
language = "c"
|
||||
standard = "c11"
|
||||
sources = "*.c"
|
||||
output = "sol"
|
||||
warnings = strict
|
||||
}
|
||||
group("g") { weight = 1.0 test("t") { stdout = "" } }
|
||||
`
|
||||
_, _, err := Parse(src)
|
||||
if err == nil || !strings.Contains(err.Error(), "cannot mix legacy") {
|
||||
t.Errorf("want mix-legacy error, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,44 @@
|
||||
package runner
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStatusString(t *testing.T) {
|
||||
cases := []struct {
|
||||
s Status
|
||||
want string
|
||||
}{
|
||||
{StatusPass, "PASS"},
|
||||
{StatusFail, "FAIL"},
|
||||
{StatusTLE, "TLE"},
|
||||
{StatusMLE, "MLE"},
|
||||
{StatusBuildError, "BUILD_ERROR"},
|
||||
{StatusRuntimeError, "RE"},
|
||||
{Status(999), "UNKNOWN"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
if got := c.s.String(); got != c.want {
|
||||
t.Errorf("Status(%d).String() = %q, want %q", c.s, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddFailureAppends(t *testing.T) {
|
||||
r := &TestResult{}
|
||||
r.addFailure("first %s", "msg")
|
||||
r.addFailure("second %d", 2)
|
||||
if len(r.Failures) != 2 {
|
||||
t.Fatalf("Failures len = %d, want 2", len(r.Failures))
|
||||
}
|
||||
if r.Failures[0] != "first msg" {
|
||||
t.Errorf("Failures[0] = %q", r.Failures[0])
|
||||
}
|
||||
if !strings.Contains(r.Failures[1], "second 2") {
|
||||
t.Errorf("Failures[1] = %q", r.Failures[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateScoreEmpty(t *testing.T) {
|
||||
r := &SuiteResult{}
|
||||
|
||||
Reference in New Issue
Block a user