From a72e70b026edea2bdbd05f6dd35f8ea40a14b48a Mon Sep 17 00:00:00 2001 From: Paulo Bittencourt Date: Sat, 5 Oct 2024 20:40:22 -0400 Subject: [PATCH] fix: inconsistent current directory resolution depending on include order (#1757) --- task_test.go | 102 ++++++++++++++++++ taskfile/node_http.go | 8 +- testdata/includes_http/child-taskfile2.yml | 9 ++ testdata/includes_http/child-taskfile3.yml | 4 + ...root-taskfile-remotefile-empty-dir-1st.yml | 8 ++ ...root-taskfile-remotefile-empty-dir-2nd.yml | 8 ++ 6 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 testdata/includes_http/child-taskfile2.yml create mode 100644 testdata/includes_http/child-taskfile3.yml create mode 100644 testdata/includes_http/root-taskfile-remotefile-empty-dir-1st.yml create mode 100644 testdata/includes_http/root-taskfile-remotefile-empty-dir-2nd.yml diff --git a/task_test.go b/task_test.go index 8b6a005e..3411c5eb 100644 --- a/task_test.go +++ b/task_test.go @@ -5,6 +5,9 @@ import ( "context" "fmt" "io" + "io/fs" + "net/http" + "net/http/httptest" "os" "path/filepath" "regexp" @@ -12,6 +15,7 @@ import ( "strings" "sync" "testing" + "time" "github.com/Masterminds/semver/v3" "github.com/stretchr/testify/assert" @@ -21,6 +25,7 @@ import ( "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/experiments" "github.com/go-task/task/v3/internal/filepathext" + "github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/taskfile/ast" ) @@ -1086,6 +1091,88 @@ func TestIncludesEmptyMain(t *testing.T) { tt.Run(t) } +func TestIncludesHttp(t *testing.T) { + enableExperimentForTest(t, &experiments.RemoteTaskfiles, "1") + + dir, err := filepath.Abs("testdata/includes_http") + require.NoError(t, err) + + srv := httptest.NewServer(http.FileServer(http.Dir(dir))) + defer srv.Close() + + t.Cleanup(func() { + // This test fills the .task/remote directory with cache entries because the include URL + // is different on every test due to the dynamic nature of the TCP port in srv.URL + if err := os.RemoveAll(filepath.Join(dir, ".task")); err != nil { + t.Logf("error cleaning up: %s", err) + } + }) + + taskfiles, err := fs.Glob(os.DirFS(dir), "root-taskfile-*.yml") + require.NoError(t, err) + + remotes := []struct { + name string + root string + }{ + { + name: "local", + root: ".", + }, + { + name: "http-remote", + root: srv.URL, + }, + } + + for _, taskfile := range taskfiles { + t.Run(taskfile, func(t *testing.T) { + for _, remote := range remotes { + t.Run(remote.name, func(t *testing.T) { + t.Setenv("INCLUDE_ROOT", remote.root) + entrypoint := filepath.Join(dir, taskfile) + + var buff SyncBuffer + e := task.Executor{ + Entrypoint: entrypoint, + Dir: dir, + Stdout: &buff, + Stderr: &buff, + Insecure: true, + Download: true, + AssumeYes: true, + Logger: &logger.Logger{Stdout: &buff, Stderr: &buff, Verbose: true}, + Timeout: time.Minute, + } + require.NoError(t, e.Setup()) + defer func() { t.Log("output:", buff.buf.String()) }() + + tcs := []struct { + name, dir string + }{ + { + name: "second-with-dir-1:third-with-dir-1:default", + dir: filepath.Join(dir, "dir-1"), + }, + { + name: "second-with-dir-1:third-with-dir-2:default", + dir: filepath.Join(dir, "dir-2"), + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + task, err := e.CompiledTask(&ast.Call{Task: tc.name}) + require.NoError(t, err) + assert.Equal(t, tc.dir, task.Dir) + }) + } + }) + } + }) + } +} + func TestIncludesDependencies(t *testing.T) { tt := fileContentTest{ Dir: "testdata/includes_deps", @@ -2616,3 +2703,18 @@ func TestReference(t *testing.T) { }) } } + +// enableExperimentForTest enables the experiment behind pointer e for the duration of test t and sub-tests, +// with the experiment being restored to its previous state when tests complete. +// +// Typically experiments are controlled via TASK_X_ env vars, but we cannot use those in tests +// because the experiment settings are parsed during experiments.init(), before any tests run. +func enableExperimentForTest(t *testing.T, e *experiments.Experiment, val string) { + prev := *e + *e = experiments.Experiment{ + Name: prev.Name, + Enabled: true, + Value: val, + } + t.Cleanup(func() { *e = prev }) +} diff --git a/taskfile/node_http.go b/taskfile/node_http.go index d6fb2201..fc905848 100644 --- a/taskfile/node_http.go +++ b/taskfile/node_http.go @@ -110,8 +110,12 @@ func (node *HTTPNode) ResolveDir(dir string) (string, error) { // NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory // This means that files are included relative to one another - entrypointDir := filepath.Dir(node.Dir()) - return filepathext.SmartJoin(entrypointDir, path), nil + parent := node.Dir() + if node.Parent() != nil { + parent = node.Parent().Dir() + } + + return filepathext.SmartJoin(parent, path), nil } func (node *HTTPNode) FilenameAndLastDir() (string, string) { diff --git a/testdata/includes_http/child-taskfile2.yml b/testdata/includes_http/child-taskfile2.yml new file mode 100644 index 00000000..84690ed5 --- /dev/null +++ b/testdata/includes_http/child-taskfile2.yml @@ -0,0 +1,9 @@ +version: '3' + +includes: + third-with-dir-1: + taskfile: "{{.INCLUDE_ROOT}}/child-taskfile3.yml" + dir: ./dir-1 + third-with-dir-2: + taskfile: "{{.INCLUDE_ROOT}}/child-taskfile3.yml" + dir: ./dir-2 diff --git a/testdata/includes_http/child-taskfile3.yml b/testdata/includes_http/child-taskfile3.yml new file mode 100644 index 00000000..3449a19b --- /dev/null +++ b/testdata/includes_http/child-taskfile3.yml @@ -0,0 +1,4 @@ +version: '3' + +tasks: + default: "true" diff --git a/testdata/includes_http/root-taskfile-remotefile-empty-dir-1st.yml b/testdata/includes_http/root-taskfile-remotefile-empty-dir-1st.yml new file mode 100644 index 00000000..ff948f39 --- /dev/null +++ b/testdata/includes_http/root-taskfile-remotefile-empty-dir-1st.yml @@ -0,0 +1,8 @@ +version: '3' + +includes: + second-no-dir: + taskfile: "{{.INCLUDE_ROOT}}/child-taskfile2.yml" + second-with-dir-1: + taskfile: "{{.INCLUDE_ROOT}}/child-taskfile2.yml" + dir: ./dir-1 diff --git a/testdata/includes_http/root-taskfile-remotefile-empty-dir-2nd.yml b/testdata/includes_http/root-taskfile-remotefile-empty-dir-2nd.yml new file mode 100644 index 00000000..69080d55 --- /dev/null +++ b/testdata/includes_http/root-taskfile-remotefile-empty-dir-2nd.yml @@ -0,0 +1,8 @@ +version: '3' + +includes: + second-with-dir-1: + taskfile: "{{.INCLUDE_ROOT}}/child-taskfile2.yml" + dir: ./dir-1 + second-no-dir: + taskfile: "{{.INCLUDE_ROOT}}/child-taskfile2.yml"