package dsl import ( "os" "path/filepath" "strings" "testing" ) func writeTempJdg(t *testing.T, dir, name, content string) string { t.Helper() path := filepath.Join(dir, name) if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatal(err) } return path } func TestIncludeBasicMerge(t *testing.T) { dir := t.TempDir() writeTempJdg(t, dir, "common.jdg", ` toolchains { gcc { platforms = "linux" } } build_defaults { language = "c" standard = "c11" sources = "*.c" output = "solution" warnings = strict } `) mainPath := writeTempJdg(t, dir, "main.jdg", ` include "common.jdg" build "release" { profile = release } 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.Toolchains) != 1 || f.Toolchains[0].Name != "gcc" { t.Errorf("toolchains not merged: %+v", f.Toolchains) } if f.BuildDefaults == nil || f.BuildDefaults.Language != "c" { t.Errorf("build_defaults not merged: %+v", f.BuildDefaults) } if len(f.Builds) != 1 || f.Builds[0].Name != "release" { t.Errorf("local build lost: %+v", f.Builds) } } func TestIncludeLocalOverridesIncluded(t *testing.T) { dir := t.TempDir() writeTempJdg(t, dir, "common.jdg", ` build_defaults { language = "c" standard = "c11" warnings = strict } `) mainPath := writeTempJdg(t, dir, "main.jdg", ` include "common.jdg" build_defaults { standard = "c17" } group("g") { weight = 1.0 test("t") { stdout = "ok\n" } } `) f, _, err := ParseFile(mainPath) if err != nil { t.Fatalf("parse: %v", err) } if f.BuildDefaults.Standard != "c17" { t.Errorf("local should override included standard: got %q", f.BuildDefaults.Standard) } if f.BuildDefaults.Language != "c" { t.Errorf("language should survive from include: got %q", f.BuildDefaults.Language) } if f.BuildDefaults.Warnings != WarningsStrict { t.Errorf("warnings should survive from include: got %v", f.BuildDefaults.Warnings) } } func TestIncludeRelativePathResolution(t *testing.T) { dir := t.TempDir() writeTempJdg(t, dir, "shared/tools.jdg", ` toolchains { gcc { platforms = "linux" } } `) mainPath := writeTempJdg(t, dir, "suite/main.jdg", ` include "../shared/tools.jdg" build "release" { language = "c" sources = "solution.c" output = "solution" profile = release } 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.Toolchains) != 1 { t.Errorf("relative include failed to resolve: %+v", f.Toolchains) } } func TestIncludeDuplicateToolchainErrors(t *testing.T) { dir := t.TempDir() writeTempJdg(t, dir, "common.jdg", ` toolchains { gcc { platforms = "linux" } } `) mainPath := writeTempJdg(t, dir, "main.jdg", ` include "common.jdg" toolchains { gcc { platforms = "linux" } } group("g") { weight = 1.0 test("t") { stdout = "ok\n" } } `) _, _, err := ParseFile(mainPath) if err == nil { t.Fatal("expected duplicate toolchain error") } if !strings.Contains(err.Error(), "duplicate toolchain") { t.Errorf("error %q does not mention duplicate toolchain", err.Error()) } } func TestIncludeCircularDetection(t *testing.T) { dir := t.TempDir() aPath := writeTempJdg(t, dir, "a.jdg", ` include "b.jdg" group("ga") { weight = 1.0 test("t") { stdout = "ok\n" } } `) writeTempJdg(t, dir, "b.jdg", ` include "a.jdg" `) _, _, err := ParseFile(aPath) if err == nil { t.Fatal("expected circular include error") } if !strings.Contains(err.Error(), "circular include") { t.Errorf("error %q does not mention circular include", err.Error()) } } func TestIncludeMissingFileErrors(t *testing.T) { dir := t.TempDir() mainPath := writeTempJdg(t, dir, "main.jdg", ` include "nonexistent.jdg" group("g") { weight = 1.0 test("t") { stdout = "ok\n" } } `) _, _, err := ParseFile(mainPath) if err == nil { t.Fatal("expected missing include error") } if !strings.Contains(err.Error(), "nonexistent.jdg") { t.Errorf("error %q does not reference the missing path", err.Error()) } } func TestParseRejectsIncludeWithoutFileContext(t *testing.T) { src := ` include "common.jdg" group("g") { weight = 1.0 test("t") { stdout = "ok\n" } } ` _, _, err := Parse(src) if err == nil { t.Fatal("expected error: include without file context") } if !strings.Contains(err.Error(), "file context") { t.Errorf("error %q does not mention file context", err.Error()) } }