- 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).
295 lines
8.0 KiB
Go
295 lines
8.0 KiB
Go
package runner
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Mond1c/judge/dsl"
|
|
)
|
|
|
|
func TestResolveToolchainKnown(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
wantClass CompilerClass
|
|
wantName string
|
|
}{
|
|
{"gcc", CompilerGNU, "gcc"},
|
|
{"g++", CompilerGNU, "gcc"},
|
|
{"clang", CompilerGNU, "clang"},
|
|
{"clang++", CompilerGNU, "clang"},
|
|
{"cl", CompilerMSVC, "msvc"},
|
|
{"cl.exe", CompilerMSVC, "msvc"},
|
|
{"msvc", CompilerMSVC, "msvc"},
|
|
{"clang-cl", CompilerMSVC, "clang-cl"},
|
|
{"cc", CompilerGNU, "cc"},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.in, func(t *testing.T) {
|
|
tc := ResolveToolchain(c.in)
|
|
if tc.Class != c.wantClass {
|
|
t.Errorf("class: got %v, want %v", tc.Class, c.wantClass)
|
|
}
|
|
if tc.Name != c.wantName {
|
|
t.Errorf("name: got %q, want %q", tc.Name, c.wantName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveToolchainSpec(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
spec dsl.ToolchainSpec
|
|
wantClass CompilerClass
|
|
wantBin string
|
|
wantName string
|
|
}{
|
|
{
|
|
"gcc inferred",
|
|
dsl.ToolchainSpec{Name: "gcc", Platforms: []string{"linux"}},
|
|
CompilerGNU, "gcc", "gcc",
|
|
},
|
|
{
|
|
"msvc inferred",
|
|
dsl.ToolchainSpec{Name: "msvc", Platforms: []string{"windows"}},
|
|
CompilerMSVC, "cl", "msvc",
|
|
},
|
|
{
|
|
"nvcc with explicit class",
|
|
dsl.ToolchainSpec{Name: "nvcc", Platforms: []string{"linux"}, Class: "gnu"},
|
|
CompilerGNU, "nvcc", "nvcc",
|
|
},
|
|
{
|
|
"custom binary override",
|
|
dsl.ToolchainSpec{Name: "clang", Platforms: []string{"linux", "windows"}, Binary: "clang-17"},
|
|
CompilerGNU, "clang-17", "clang",
|
|
},
|
|
{
|
|
"unknown name, explicit class",
|
|
dsl.ToolchainSpec{Name: "hipcc", Platforms: []string{"linux"}, Class: "gnu"},
|
|
CompilerGNU, "hipcc", "hipcc",
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
got := ResolveToolchainSpec(&c.spec)
|
|
if got.Class != c.wantClass {
|
|
t.Errorf("class: got %v, want %v", got.Class, c.wantClass)
|
|
}
|
|
if got.Binary != c.wantBin {
|
|
t.Errorf("binary: got %q, want %q", got.Binary, c.wantBin)
|
|
}
|
|
if got.Name != c.wantName {
|
|
t.Errorf("name: got %q, want %q", got.Name, c.wantName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveToolchainUnknownFallsBackToGNU(t *testing.T) {
|
|
tc := ResolveToolchain("gcc-13")
|
|
if tc.Class != CompilerGNU {
|
|
t.Errorf("unknown compiler should fall back to GNU, got %v", tc.Class)
|
|
}
|
|
if tc.Binary != "gcc-13" {
|
|
t.Errorf("binary should preserve the original string, got %q", tc.Binary)
|
|
}
|
|
if tc.Name != "gcc-13" {
|
|
t.Errorf("name should be the lowercased original, got %q", tc.Name)
|
|
}
|
|
}
|
|
|
|
func TestCompileGNURelease(t *testing.T) {
|
|
cfg := dsl.BuildConfig{
|
|
Language: "c",
|
|
Standard: "c11",
|
|
Sources: []string{"solution.c"},
|
|
Profile: dsl.ProfileRelease,
|
|
Warnings: dsl.WarningsStrict,
|
|
}
|
|
tc := Toolchain{Class: CompilerGNU, Binary: "gcc", Name: "gcc"}
|
|
argv, err := Compile(cfg, tc, "solution")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := []string{"gcc", "-std=c11", "-O2", "-Wall", "-Wextra", "solution.c", "-o", "solution"}
|
|
if !reflect.DeepEqual(argv, want) {
|
|
t.Errorf("argv =\n %v\nwant\n %v", argv, want)
|
|
}
|
|
}
|
|
|
|
func TestCompileGNUDebug(t *testing.T) {
|
|
cfg := dsl.BuildConfig{
|
|
Standard: "c11",
|
|
Sources: []string{"a.c", "b.c"},
|
|
Profile: dsl.ProfileDebug,
|
|
}
|
|
tc := Toolchain{Class: CompilerGNU, Binary: "gcc"}
|
|
argv, _ := Compile(cfg, tc, "out")
|
|
if !containsSubsequence(argv, []string{"-O0", "-g"}) {
|
|
t.Errorf("debug flags missing: %v", argv)
|
|
}
|
|
if !containsSubsequence(argv, []string{"a.c", "b.c", "-o", "out"}) {
|
|
t.Errorf("sources and output order wrong: %v", argv)
|
|
}
|
|
}
|
|
|
|
func TestCompileGNUSanitized(t *testing.T) {
|
|
cfg := dsl.BuildConfig{
|
|
Standard: "c11",
|
|
Sources: []string{"s.c"},
|
|
Profile: dsl.ProfileSanitized,
|
|
Sanitize: []string{"address", "undefined"},
|
|
}
|
|
tc := Toolchain{Class: CompilerGNU, Binary: "clang"}
|
|
argv, _ := Compile(cfg, tc, "s")
|
|
joined := strings.Join(argv, " ")
|
|
if !strings.Contains(joined, "-fsanitize=address,undefined") {
|
|
t.Errorf("sanitize flag missing: %v", argv)
|
|
}
|
|
if !strings.Contains(joined, "-O1") {
|
|
t.Errorf("-O1 missing for sanitized profile: %v", argv)
|
|
}
|
|
}
|
|
|
|
func TestCompileGNUIncludesAndDefinesAndLink(t *testing.T) {
|
|
cfg := dsl.BuildConfig{
|
|
Sources: []string{"m.c"},
|
|
Includes: []string{"include", "vendor/inc"},
|
|
Defines: map[string]string{"FOO": "1", "BAR": ""},
|
|
Link: []string{"m", "pthread"},
|
|
Extra: []string{"-fno-strict-aliasing"},
|
|
}
|
|
tc := Toolchain{Class: CompilerGNU, Binary: "gcc"}
|
|
argv, _ := Compile(cfg, tc, "out")
|
|
|
|
joined := strings.Join(argv, " ")
|
|
for _, want := range []string{"-Iinclude", "-Ivendor/inc", "-DFOO=1", "-DBAR", "-lm", "-lpthread", "-fno-strict-aliasing"} {
|
|
if !strings.Contains(joined, want) {
|
|
t.Errorf("missing %q in %v", want, argv)
|
|
}
|
|
}
|
|
|
|
oIdx := indexOf(argv, "-o")
|
|
lmIdx := indexOf(argv, "-lm")
|
|
if oIdx == -1 || lmIdx == -1 || lmIdx < oIdx {
|
|
t.Errorf("-lm must come after -o: %v", argv)
|
|
}
|
|
}
|
|
|
|
func TestCompileGNUDefinesOrderDeterministic(t *testing.T) {
|
|
cfg := dsl.BuildConfig{
|
|
Sources: []string{"s.c"},
|
|
Defines: map[string]string{"Z": "1", "A": "2", "M": "3"},
|
|
}
|
|
tc := Toolchain{Class: CompilerGNU, Binary: "gcc"}
|
|
argv1, _ := Compile(cfg, tc, "s")
|
|
for range 20 {
|
|
argv2, _ := Compile(cfg, tc, "s")
|
|
if !reflect.DeepEqual(argv1, argv2) {
|
|
t.Fatalf("defines order not deterministic:\n %v\n %v", argv1, argv2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompileMSVCRelease(t *testing.T) {
|
|
cfg := dsl.BuildConfig{
|
|
Standard: "c11",
|
|
Sources: []string{"solution.c"},
|
|
Profile: dsl.ProfileRelease,
|
|
Warnings: dsl.WarningsStrict,
|
|
}
|
|
tc := Toolchain{Class: CompilerMSVC, Binary: "cl", Name: "msvc"}
|
|
argv, _ := Compile(cfg, tc, "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) {
|
|
t.Errorf("argv =\n %v\nwant\n %v", argv, want)
|
|
}
|
|
}
|
|
|
|
func TestCompileMSVCDebug(t *testing.T) {
|
|
cfg := dsl.BuildConfig{
|
|
Sources: []string{"s.c"},
|
|
Profile: dsl.ProfileDebug,
|
|
}
|
|
tc := Toolchain{Class: CompilerMSVC, Binary: "cl"}
|
|
argv, _ := Compile(cfg, tc, "s.exe")
|
|
joined := strings.Join(argv, " ")
|
|
for _, want := range []string{"/Od", "/Zi"} {
|
|
if !strings.Contains(joined, want) {
|
|
t.Errorf("missing %q in %v", want, argv)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompileMSVCSanitizedAddressOnly(t *testing.T) {
|
|
cfg := dsl.BuildConfig{
|
|
Sources: []string{"s.c"},
|
|
Profile: dsl.ProfileSanitized,
|
|
Sanitize: []string{"address", "undefined", "thread"},
|
|
}
|
|
tc := Toolchain{Class: CompilerMSVC, Binary: "cl"}
|
|
argv, _ := Compile(cfg, tc, "s.exe")
|
|
joined := strings.Join(argv, " ")
|
|
if !strings.Contains(joined, "/fsanitize=address") {
|
|
t.Errorf("msvc should emit /fsanitize=address for sanitized profile: %v", argv)
|
|
}
|
|
if strings.Contains(joined, "undefined") || strings.Contains(joined, "thread") {
|
|
t.Errorf("msvc should drop unsupported sanitizers, got: %v", argv)
|
|
}
|
|
}
|
|
|
|
func TestCompileMSVCIncludesAndDefines(t *testing.T) {
|
|
cfg := dsl.BuildConfig{
|
|
Sources: []string{"m.c"},
|
|
Includes: []string{"include"},
|
|
Defines: map[string]string{"FOO": "1", "BAR": ""},
|
|
}
|
|
tc := Toolchain{Class: CompilerMSVC, Binary: "cl"}
|
|
argv, _ := Compile(cfg, tc, "m.exe")
|
|
joined := strings.Join(argv, " ")
|
|
for _, want := range []string{"/Iinclude", "/DFOO=1", "/DBAR"} {
|
|
if !strings.Contains(joined, want) {
|
|
t.Errorf("missing %q in %v", want, argv)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompileUnknownClassErrors(t *testing.T) {
|
|
cfg := dsl.BuildConfig{Sources: []string{"s.c"}}
|
|
tc := Toolchain{Class: CompilerUnknown, Binary: "weird"}
|
|
if _, err := Compile(cfg, tc, "s"); err == nil {
|
|
t.Error("expected error for unknown compiler class")
|
|
}
|
|
}
|
|
|
|
func containsSubsequence(haystack, needle []string) bool {
|
|
if len(needle) == 0 {
|
|
return true
|
|
}
|
|
for i := 0; i+len(needle) <= len(haystack); i++ {
|
|
match := true
|
|
for j := range needle {
|
|
if haystack[i+j] != needle[j] {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
if match {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func indexOf(xs []string, x string) int {
|
|
for i, v := range xs {
|
|
if v == x {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|