All checks were successful
build-dsl-smoke / Discover matrix (push) Successful in 8s
build-dsl-smoke / Build judge (push) Successful in 11s
build-dsl-smoke / ${{ matrix.cell.build }} / ${{ matrix.cell.toolchain }} / ${{ matrix.cell.platform }} (push) Successful in 5s
memory-limit / Build judge (pull_request) Successful in 10s
build-dsl-smoke / SUMMARY (push) Successful in 3s
memory-limit / Linux / gcc (pull_request) Successful in 9s
memory-limit / Linux / clang (pull_request) Successful in 13s
memory-limit / Windows / clang (pull_request) Successful in 16s
memory-limit / Windows / msvc (pull_request) Successful in 17s
456 lines
11 KiB
Go
456 lines
11 KiB
Go
package dsl
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseBuildBlockMinimal(t *testing.T) {
|
|
src := `
|
|
build_defaults {
|
|
language = "c"
|
|
standard = "c11"
|
|
sources = "*.c"
|
|
output = "solution"
|
|
warnings = strict
|
|
}
|
|
|
|
build "release" {
|
|
profile = release
|
|
}
|
|
|
|
build "debug" {
|
|
profile = debug
|
|
}
|
|
|
|
group("g1") {
|
|
weight = 1.0
|
|
test("t1") {
|
|
stdout = "ok\n"
|
|
}
|
|
}
|
|
`
|
|
f, _, err := Parse(src)
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
|
|
if f.BuildDefaults == nil {
|
|
t.Fatal("BuildDefaults not populated")
|
|
}
|
|
if f.BuildDefaults.Language != "c" {
|
|
t.Errorf("language = %q", f.BuildDefaults.Language)
|
|
}
|
|
if f.BuildDefaults.Standard != "c11" {
|
|
t.Errorf("standard = %q", f.BuildDefaults.Standard)
|
|
}
|
|
if len(f.BuildDefaults.Sources) != 1 || f.BuildDefaults.Sources[0] != "*.c" {
|
|
t.Errorf("sources = %v", f.BuildDefaults.Sources)
|
|
}
|
|
if f.BuildDefaults.Output != "solution" {
|
|
t.Errorf("output = %q", f.BuildDefaults.Output)
|
|
}
|
|
if f.BuildDefaults.Warnings != WarningsStrict {
|
|
t.Errorf("warnings = %v", f.BuildDefaults.Warnings)
|
|
}
|
|
|
|
if len(f.Builds) != 2 {
|
|
t.Fatalf("expected 2 builds, got %d", len(f.Builds))
|
|
}
|
|
if f.Builds[0].Name != "release" || f.Builds[0].Profile != ProfileRelease {
|
|
t.Errorf("builds[0] = %+v", f.Builds[0])
|
|
}
|
|
if f.Builds[1].Name != "debug" || f.Builds[1].Profile != ProfileDebug {
|
|
t.Errorf("builds[1] = %+v", f.Builds[1])
|
|
}
|
|
}
|
|
|
|
func TestParseBuildLegacyStillWorks(t *testing.T) {
|
|
src := `
|
|
build "cc -O2 solution.c -o solution"
|
|
timeout 5s
|
|
|
|
group("g1") {
|
|
weight = 1.0
|
|
test("t1") { stdout = "ok\n" }
|
|
}
|
|
`
|
|
f, _, err := Parse(src)
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
if f.Build != "cc -O2 solution.c -o solution" {
|
|
t.Errorf("legacy build = %q", f.Build)
|
|
}
|
|
if f.BuildDefaults != nil || len(f.Builds) != 0 {
|
|
t.Errorf("structured fields should be empty for legacy form")
|
|
}
|
|
}
|
|
|
|
func TestParseBuildOSOverride(t *testing.T) {
|
|
src := `
|
|
build_defaults {
|
|
language = "c"
|
|
standard = "c11"
|
|
sources = "*.c"
|
|
output = "solution"
|
|
}
|
|
|
|
build "release" {
|
|
profile = release
|
|
linux { extra = "-fPIC" }
|
|
windows { extra = "/bigobj" }
|
|
}
|
|
|
|
group("g1") {
|
|
weight = 1.0
|
|
test("t1") { stdout = "ok\n" }
|
|
}
|
|
`
|
|
f, _, err := Parse(src)
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
b := f.Builds[0]
|
|
if b.Linux == nil || len(b.Linux.Extra) != 1 || b.Linux.Extra[0] != "-fPIC" {
|
|
t.Errorf("linux override = %+v", b.Linux)
|
|
}
|
|
if b.Windows == nil || len(b.Windows.Extra) != 1 || b.Windows.Extra[0] != "/bigobj" {
|
|
t.Errorf("windows override = %+v", b.Windows)
|
|
}
|
|
if b.Darwin != nil {
|
|
t.Errorf("darwin override should be nil")
|
|
}
|
|
}
|
|
|
|
func TestParseBuildPlatformsFilter(t *testing.T) {
|
|
src := `
|
|
build "sanitized" {
|
|
profile = sanitized
|
|
sanitize = "address" "undefined"
|
|
platforms = "linux"
|
|
compilers = "gcc" "clang"
|
|
}
|
|
|
|
group("g1") {
|
|
weight = 1.0
|
|
test("t1") { stdout = "ok\n" }
|
|
}
|
|
`
|
|
f, _, err := Parse(src)
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
b := f.Builds[0]
|
|
if b.Profile != ProfileSanitized {
|
|
t.Errorf("profile = %v", b.Profile)
|
|
}
|
|
if len(b.Sanitize) != 2 || b.Sanitize[0] != "address" || b.Sanitize[1] != "undefined" {
|
|
t.Errorf("sanitize = %v", b.Sanitize)
|
|
}
|
|
if len(b.Platforms) != 1 || b.Platforms[0] != "linux" {
|
|
t.Errorf("platforms = %v", b.Platforms)
|
|
}
|
|
if len(b.Compilers) != 2 {
|
|
t.Errorf("compilers = %v", b.Compilers)
|
|
}
|
|
}
|
|
|
|
func TestParseBuildDefineField(t *testing.T) {
|
|
src := `
|
|
build "rel" {
|
|
profile = release
|
|
define("NDEBUG") = "1"
|
|
define("VERSION") = "42"
|
|
}
|
|
|
|
group("g1") {
|
|
weight = 1.0
|
|
test("t1") { stdout = "ok\n" }
|
|
}
|
|
`
|
|
f, _, err := Parse(src)
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
d := f.Builds[0].Defines
|
|
if d["NDEBUG"] != "1" || d["VERSION"] != "42" {
|
|
t.Errorf("defines = %v", d)
|
|
}
|
|
}
|
|
|
|
func TestParseBuildErrors(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
src string
|
|
want string
|
|
}{
|
|
{
|
|
name: "unknown profile",
|
|
src: `
|
|
build "x" { profile = ultra }
|
|
group("g") { weight=1.0 test("t"){ stdout="ok\n" } }
|
|
`,
|
|
want: "unknown profile",
|
|
},
|
|
{
|
|
name: "unknown warnings",
|
|
src: `
|
|
build "x" { warnings = insane }
|
|
group("g") { weight=1.0 test("t"){ stdout="ok\n" } }
|
|
`,
|
|
want: "unknown warnings",
|
|
},
|
|
{
|
|
name: "unknown platform",
|
|
src: `
|
|
build "x" { platforms = "bsd" }
|
|
group("g") { weight=1.0 test("t"){ stdout="ok\n" } }
|
|
`,
|
|
want: "unknown platform",
|
|
},
|
|
{
|
|
name: "nested OS override",
|
|
src: `
|
|
build "x" {
|
|
linux {
|
|
windows { extra = "nope" }
|
|
}
|
|
}
|
|
group("g") { weight=1.0 test("t"){ stdout="ok\n" } }
|
|
`,
|
|
want: "cannot be nested",
|
|
},
|
|
{
|
|
name: "duplicate OS override",
|
|
src: `
|
|
build "x" {
|
|
linux { extra = "-a" }
|
|
linux { extra = "-b" }
|
|
}
|
|
group("g") { weight=1.0 test("t"){ stdout="ok\n" } }
|
|
`,
|
|
want: "duplicate linux override",
|
|
},
|
|
{
|
|
name: "unknown field",
|
|
src: `
|
|
build "x" { magic = "yes" }
|
|
group("g") { weight=1.0 test("t"){ stdout="ok\n" } }
|
|
`,
|
|
want: "unknown field",
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
_, _, err := Parse(c.src)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), c.want) {
|
|
t.Errorf("error %q does not contain %q", err.Error(), c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseBuildMixedLegacyAndStructuredRejected(t *testing.T) {
|
|
src := `
|
|
build "cc -O2 solution.c -o solution"
|
|
build "release" { profile = release }
|
|
group("g") { weight = 1.0 test("t") { stdout = "ok\n" } }
|
|
`
|
|
_, _, err := Parse(src)
|
|
if err == nil {
|
|
t.Fatal("expected error mixing legacy and structured builds")
|
|
}
|
|
if !strings.Contains(err.Error(), "cannot mix") {
|
|
t.Errorf("error %q does not mention mixing", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestParseBuildDuplicateNameRejected(t *testing.T) {
|
|
src := `
|
|
build "release" { profile = release }
|
|
build "release" { profile = debug }
|
|
group("g") { weight = 1.0 test("t") { stdout = "ok\n" } }
|
|
`
|
|
_, _, err := Parse(src)
|
|
if err == nil {
|
|
t.Fatal("expected error on duplicate build name")
|
|
}
|
|
if !strings.Contains(err.Error(), "duplicate") {
|
|
t.Errorf("error %q does not mention duplicate", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestBuildConfigResolveMerge(t *testing.T) {
|
|
defaults := &BuildConfig{
|
|
Language: "c",
|
|
Standard: "c11",
|
|
Sources: []string{"*.c"},
|
|
Output: "solution",
|
|
Warnings: WarningsStrict,
|
|
}
|
|
b := &BuildConfig{
|
|
Name: "release",
|
|
Profile: ProfileRelease,
|
|
Extra: []string{"-DFOO"},
|
|
Linux: &BuildConfig{
|
|
Extra: []string{"-fPIC"},
|
|
},
|
|
Windows: &BuildConfig{
|
|
Extra: []string{"/bigobj"},
|
|
Output: "solution",
|
|
},
|
|
}
|
|
|
|
linux := b.Resolve(defaults, "linux")
|
|
if linux.Language != "c" || linux.Standard != "c11" {
|
|
t.Errorf("linux defaults not merged: %+v", linux)
|
|
}
|
|
if linux.Profile != ProfileRelease {
|
|
t.Errorf("linux profile = %v", linux.Profile)
|
|
}
|
|
if linux.Warnings != WarningsStrict {
|
|
t.Errorf("linux warnings = %v", linux.Warnings)
|
|
}
|
|
if len(linux.Sources) != 1 || linux.Sources[0] != "*.c" {
|
|
t.Errorf("linux sources = %v", linux.Sources)
|
|
}
|
|
if len(linux.Extra) != 2 || linux.Extra[0] != "-DFOO" || linux.Extra[1] != "-fPIC" {
|
|
t.Errorf("linux extra = %v", linux.Extra)
|
|
}
|
|
|
|
windows := b.Resolve(defaults, "windows")
|
|
if len(windows.Extra) != 2 || windows.Extra[0] != "-DFOO" || windows.Extra[1] != "/bigobj" {
|
|
t.Errorf("windows extra = %v", windows.Extra)
|
|
}
|
|
|
|
darwin := b.Resolve(defaults, "darwin")
|
|
if len(darwin.Extra) != 1 || darwin.Extra[0] != "-DFOO" {
|
|
t.Errorf("darwin extra (no override) = %v", darwin.Extra)
|
|
}
|
|
|
|
if len(b.Extra) != 1 {
|
|
t.Errorf("receiver mutated: %v", b.Extra)
|
|
}
|
|
}
|
|
|
|
func TestParseToolchainsBlock(t *testing.T) {
|
|
src := `
|
|
toolchains {
|
|
gcc { platforms = "linux" }
|
|
clang {
|
|
platforms = "linux" "windows"
|
|
binary = "clang-17"
|
|
class = gnu
|
|
}
|
|
msvc { platforms = "windows" }
|
|
nvcc {
|
|
platforms = "linux"
|
|
class = gnu
|
|
}
|
|
}
|
|
|
|
build "release" { profile = release }
|
|
|
|
group("g") { weight = 1.0 test("t") { stdout = "ok\n" } }
|
|
`
|
|
f, _, err := Parse(src)
|
|
if err != nil {
|
|
t.Fatalf("parse: %v", err)
|
|
}
|
|
if len(f.Toolchains) != 4 {
|
|
t.Fatalf("expected 4 toolchains, got %d", len(f.Toolchains))
|
|
}
|
|
gcc := f.Toolchains[0]
|
|
if gcc.Name != "gcc" || len(gcc.Platforms) != 1 || gcc.Platforms[0] != "linux" {
|
|
t.Errorf("gcc = %+v", gcc)
|
|
}
|
|
clang := f.Toolchains[1]
|
|
if clang.Name != "clang" || len(clang.Platforms) != 2 || clang.Binary != "clang-17" || clang.Class != "gnu" {
|
|
t.Errorf("clang = %+v", clang)
|
|
}
|
|
if clang.Platforms[0] != "linux" || clang.Platforms[1] != "windows" {
|
|
t.Errorf("clang platforms = %v", clang.Platforms)
|
|
}
|
|
nvcc := f.Toolchains[3]
|
|
if nvcc.Name != "nvcc" || len(nvcc.Platforms) != 1 || nvcc.Platforms[0] != "linux" || nvcc.Class != "gnu" {
|
|
t.Errorf("nvcc = %+v", nvcc)
|
|
}
|
|
}
|
|
|
|
func TestParseToolchainsErrors(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
src string
|
|
want string
|
|
}{
|
|
{
|
|
"missing platforms",
|
|
`toolchains { gcc { } }
|
|
group("g") { weight=1.0 test("t") { stdout="ok\n" } }`,
|
|
"platforms is required",
|
|
},
|
|
{
|
|
"unknown platform",
|
|
`toolchains { gcc { platforms = "bsd" } }
|
|
group("g") { weight=1.0 test("t") { stdout="ok\n" } }`,
|
|
"unknown platform",
|
|
},
|
|
{
|
|
"unknown class",
|
|
`toolchains { gcc { platforms = "linux" class = llvm } }
|
|
group("g") { weight=1.0 test("t") { stdout="ok\n" } }`,
|
|
"unknown compiler class",
|
|
},
|
|
{
|
|
"duplicate toolchain",
|
|
`toolchains {
|
|
gcc { platforms = "linux" }
|
|
gcc { platforms = "linux" }
|
|
}
|
|
group("g") { weight=1.0 test("t") { stdout="ok\n" } }`,
|
|
"duplicate toolchain",
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
_, _, err := Parse(c.src)
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), c.want) {
|
|
t.Errorf("error %q does not contain %q", err.Error(), c.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildConfigAppliesTo(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
b BuildConfig
|
|
os, cc string
|
|
expected bool
|
|
}{
|
|
{"no filters", BuildConfig{}, "linux", "gcc", true},
|
|
{"os match", BuildConfig{Platforms: []string{"linux"}}, "linux", "gcc", true},
|
|
{"os mismatch", BuildConfig{Platforms: []string{"linux"}}, "windows", "msvc", false},
|
|
{"cc match", BuildConfig{Compilers: []string{"gcc", "clang"}}, "linux", "clang", true},
|
|
{"cc mismatch", BuildConfig{Compilers: []string{"gcc"}}, "linux", "msvc", false},
|
|
{"both match", BuildConfig{Platforms: []string{"linux"}, Compilers: []string{"gcc"}}, "linux", "gcc", true},
|
|
{"os ok cc bad", BuildConfig{Platforms: []string{"linux"}, Compilers: []string{"gcc"}}, "linux", "clang", false},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
got := c.b.AppliesTo(c.os, c.cc)
|
|
if got != c.expected {
|
|
t.Errorf("AppliesTo(%q, %q) = %v, want %v", c.os, c.cc, got, c.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|