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 i := 0; i < 20; i++ { 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") want := []string{"cl", "/nologo", "/std:c11", "/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 }