diff --git a/executor_test.go b/executor_test.go index d810e652..89c28777 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1180,3 +1180,69 @@ func TestIf(t *testing.T) { NewExecutorTest(t, opts...) } } + +func TestScopedIncludes(t *testing.T) { + t.Parallel() + + // Legacy tests (without experiment) - vars should be merged globally + t.Run("legacy", func(t *testing.T) { + // Test with scoped includes disabled (legacy) - vars should be merged globally + NewExecutorTest(t, + WithName("default"), + WithExecutorOptions( + task.WithDir("testdata/scoped_includes"), + task.WithSilent(true), + ), + ) + // In legacy mode, UNIQUE_B should be accessible (merged globally) + NewExecutorTest(t, + WithName("cross-include"), + WithExecutorOptions( + task.WithDir("testdata/scoped_includes"), + task.WithSilent(true), + ), + WithTask("a:try-access-b"), + ) + }) + + // Scoped tests (with experiment enabled) - vars should be isolated + t.Run("scoped", func(t *testing.T) { + enableExperimentForTest(t, &experiments.ScopedIncludes, 1) + + // Test with scoped includes enabled - vars should be isolated + NewExecutorTest(t, + WithName("default"), + WithExecutorOptions( + task.WithDir("testdata/scoped_includes"), + task.WithSilent(true), + ), + ) + // Test inheritance: include can access root vars + NewExecutorTest(t, + WithName("inheritance-a"), + WithExecutorOptions( + task.WithDir("testdata/scoped_includes"), + task.WithSilent(true), + ), + WithTask("a:print"), + ) + // Test isolation: each include sees its own vars + NewExecutorTest(t, + WithName("isolation-b"), + WithExecutorOptions( + task.WithDir("testdata/scoped_includes"), + task.WithSilent(true), + ), + WithTask("b:print"), + ) + // In scoped mode, UNIQUE_B should be empty (isolated) + NewExecutorTest(t, + WithName("cross-include"), + WithExecutorOptions( + task.WithDir("testdata/scoped_includes"), + task.WithSilent(true), + ), + WithTask("a:try-access-b"), + ) + }) +} diff --git a/testdata/scoped_includes/Taskfile.yml b/testdata/scoped_includes/Taskfile.yml new file mode 100644 index 00000000..d8b76ccc --- /dev/null +++ b/testdata/scoped_includes/Taskfile.yml @@ -0,0 +1,20 @@ +version: "3" + +vars: + ROOT_VAR: from_root + +includes: + a: ./inc_a + b: ./inc_b + +tasks: + default: + desc: Test scoped includes - vars should be isolated + cmds: + - task: a:print + - task: b:print + + print-root-var: + desc: Print ROOT_VAR from root + cmds: + - echo "ROOT_VAR={{.ROOT_VAR}}" diff --git a/testdata/scoped_includes/inc_a/Taskfile.yml b/testdata/scoped_includes/inc_a/Taskfile.yml new file mode 100644 index 00000000..384c81af --- /dev/null +++ b/testdata/scoped_includes/inc_a/Taskfile.yml @@ -0,0 +1,18 @@ +version: "3" + +vars: + VAR: value_from_a + UNIQUE_A: only_in_a + +tasks: + print: + desc: Print vars from include A + cmds: + - echo "A:VAR={{.VAR}}" + - echo "A:UNIQUE_A={{.UNIQUE_A}}" + - echo "A:ROOT_VAR={{.ROOT_VAR}}" + + try-access-b: + desc: Try to access B's unique var (should fail in scoped mode) + cmds: + - echo "A:UNIQUE_B={{.UNIQUE_B}}" diff --git a/testdata/scoped_includes/inc_b/Taskfile.yml b/testdata/scoped_includes/inc_b/Taskfile.yml new file mode 100644 index 00000000..662d325f --- /dev/null +++ b/testdata/scoped_includes/inc_b/Taskfile.yml @@ -0,0 +1,13 @@ +version: "3" + +vars: + VAR: value_from_b + UNIQUE_B: only_in_b + +tasks: + print: + desc: Print vars from include B + cmds: + - echo "B:VAR={{.VAR}}" + - echo "B:UNIQUE_B={{.UNIQUE_B}}" + - echo "B:ROOT_VAR={{.ROOT_VAR}}" diff --git a/testdata/scoped_includes/testdata/TestScopedIncludes-legacy-cross-include.golden b/testdata/scoped_includes/testdata/TestScopedIncludes-legacy-cross-include.golden new file mode 100644 index 00000000..5b2b94fc --- /dev/null +++ b/testdata/scoped_includes/testdata/TestScopedIncludes-legacy-cross-include.golden @@ -0,0 +1 @@ +A:UNIQUE_B=only_in_b diff --git a/testdata/scoped_includes/testdata/TestScopedIncludes-legacy-default.golden b/testdata/scoped_includes/testdata/TestScopedIncludes-legacy-default.golden new file mode 100644 index 00000000..bc8e9c9d --- /dev/null +++ b/testdata/scoped_includes/testdata/TestScopedIncludes-legacy-default.golden @@ -0,0 +1,6 @@ +A:VAR=value_from_b +A:UNIQUE_A=only_in_a +A:ROOT_VAR=from_root +B:VAR=value_from_b +B:UNIQUE_B=only_in_b +B:ROOT_VAR=from_root diff --git a/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-cross-include.golden b/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-cross-include.golden new file mode 100644 index 00000000..0dcb063b --- /dev/null +++ b/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-cross-include.golden @@ -0,0 +1 @@ +A:UNIQUE_B= diff --git a/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-default.golden b/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-default.golden new file mode 100644 index 00000000..c00cf296 --- /dev/null +++ b/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-default.golden @@ -0,0 +1,6 @@ +A:VAR=value_from_a +A:UNIQUE_A=only_in_a +A:ROOT_VAR=from_root +B:VAR=value_from_b +B:UNIQUE_B=only_in_b +B:ROOT_VAR=from_root diff --git a/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-inheritance-a.golden b/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-inheritance-a.golden new file mode 100644 index 00000000..b047ffab --- /dev/null +++ b/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-inheritance-a.golden @@ -0,0 +1,3 @@ +A:VAR=value_from_a +A:UNIQUE_A=only_in_a +A:ROOT_VAR=from_root diff --git a/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-isolation-b.golden b/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-isolation-b.golden new file mode 100644 index 00000000..47f57b19 --- /dev/null +++ b/testdata/scoped_includes/testdata/TestScopedIncludes-scoped-isolation-b.golden @@ -0,0 +1,3 @@ +B:VAR=value_from_b +B:UNIQUE_B=only_in_b +B:ROOT_VAR=from_root