Files
judge/runner/compiler_test.go
Mikhail Kornilovich 5e0effc6fe refactor: extract expander helpers and Test stdio/file setters
- 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).
2026-04-16 00:28:30 +03:00

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
}