diff options
| author | 2023-04-28 01:36:44 +0800 | |
|---|---|---|
| committer | 2023-04-28 01:36:44 +0800 | |
| commit | dd84b9d64fb98746a230cd24233ff50a562c39c9 (patch) | |
| tree | b583261ef00b3afe72ec4d6dacb31e57779a6faf /cli/internal/hashing/package_deps_hash_test.go | |
| parent | 0b46fcd72ac34382387b2bcf9095233efbcc52f4 (diff) | |
| download | HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.tar.gz HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.zip | |
Diffstat (limited to 'cli/internal/hashing/package_deps_hash_test.go')
| -rw-r--r-- | cli/internal/hashing/package_deps_hash_test.go | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/cli/internal/hashing/package_deps_hash_test.go b/cli/internal/hashing/package_deps_hash_test.go new file mode 100644 index 0000000..8f68d38 --- /dev/null +++ b/cli/internal/hashing/package_deps_hash_test.go @@ -0,0 +1,386 @@ +package hashing + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" + + "github.com/vercel/turbo/cli/internal/fs" + "github.com/vercel/turbo/cli/internal/turbopath" + "gotest.tools/v3/assert" +) + +func getFixture(id int) turbopath.AbsoluteSystemPath { + cwd, _ := os.Getwd() + root := turbopath.AbsoluteSystemPath(filepath.VolumeName(cwd) + string(os.PathSeparator)) + checking := turbopath.AbsoluteSystemPath(cwd) + + for checking != root { + fixtureDirectory := checking.Join("fixtures") + _, err := os.Stat(fixtureDirectory.ToString()) + if !errors.Is(err, os.ErrNotExist) { + // Found the fixture directory! + files, _ := os.ReadDir(fixtureDirectory.ToString()) + + // Grab the specified fixture. + for _, file := range files { + fileName := turbopath.RelativeSystemPath(file.Name()) + if strings.Index(fileName.ToString(), fmt.Sprintf("%02d-", id)) == 0 { + return turbopath.AbsoluteSystemPath(fixtureDirectory.Join(fileName)) + } + } + } + checking = checking.Join("..") + } + + panic("fixtures not found!") +} + +func TestSpecialCharacters(t *testing.T) { + if runtime.GOOS == "windows" { + return + } + + fixturePath := getFixture(1) + newlinePath := turbopath.AnchoredUnixPath("new\nline").ToSystemPath() + quotePath := turbopath.AnchoredUnixPath("\"quote\"").ToSystemPath() + newline := newlinePath.RestoreAnchor(fixturePath) + quote := quotePath.RestoreAnchor(fixturePath) + + // Setup + one := os.WriteFile(newline.ToString(), []byte{}, 0644) + two := os.WriteFile(quote.ToString(), []byte{}, 0644) + + // Cleanup + defer func() { + one := os.Remove(newline.ToString()) + two := os.Remove(quote.ToString()) + + if one != nil || two != nil { + return + } + }() + + // Setup error check + if one != nil || two != nil { + return + } + + tests := []struct { + name string + rootPath turbopath.AbsoluteSystemPath + filesToHash []turbopath.AnchoredSystemPath + want map[turbopath.AnchoredUnixPath]string + wantErr bool + }{ + { + name: "Quotes", + rootPath: fixturePath, + filesToHash: []turbopath.AnchoredSystemPath{ + quotePath, + }, + want: map[turbopath.AnchoredUnixPath]string{ + quotePath.ToUnixPath(): "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + }, + }, + { + name: "Newlines", + rootPath: fixturePath, + filesToHash: []turbopath.AnchoredSystemPath{ + newlinePath, + }, + want: map[turbopath.AnchoredUnixPath]string{ + newlinePath.ToUnixPath(): "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := gitHashObject(tt.rootPath, tt.filesToHash) + if (err != nil) != tt.wantErr { + t.Errorf("gitHashObject() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("gitHashObject() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_gitHashObject(t *testing.T) { + fixturePath := getFixture(1) + traversePath, err := getTraversePath(fixturePath) + if err != nil { + return + } + + tests := []struct { + name string + rootPath turbopath.AbsoluteSystemPath + filesToHash []turbopath.AnchoredSystemPath + want map[turbopath.AnchoredUnixPath]string + wantErr bool + }{ + { + name: "No paths", + rootPath: fixturePath, + filesToHash: []turbopath.AnchoredSystemPath{}, + want: map[turbopath.AnchoredUnixPath]string{}, + }, + { + name: "Absolute paths come back relative to rootPath", + rootPath: fixturePath.Join("child"), + filesToHash: []turbopath.AnchoredSystemPath{ + turbopath.AnchoredUnixPath("../root.json").ToSystemPath(), + turbopath.AnchoredUnixPath("child.json").ToSystemPath(), + turbopath.AnchoredUnixPath("grandchild/grandchild.json").ToSystemPath(), + }, + want: map[turbopath.AnchoredUnixPath]string{ + "../root.json": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "child.json": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "grandchild/grandchild.json": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + }, + }, + { + name: "Traverse outside of the repo", + rootPath: fixturePath.Join(traversePath.ToSystemPath(), ".."), + filesToHash: []turbopath.AnchoredSystemPath{ + turbopath.AnchoredUnixPath("null.json").ToSystemPath(), + }, + want: nil, + wantErr: true, + }, + { + name: "Nonexistent file", + rootPath: fixturePath, + filesToHash: []turbopath.AnchoredSystemPath{ + turbopath.AnchoredUnixPath("nonexistent.json").ToSystemPath(), + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := gitHashObject(tt.rootPath, tt.filesToHash) + if (err != nil) != tt.wantErr { + t.Errorf("gitHashObject() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("gitHashObject() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_getTraversePath(t *testing.T) { + fixturePath := getFixture(1) + + tests := []struct { + name string + rootPath turbopath.AbsoluteSystemPath + want turbopath.RelativeUnixPath + wantErr bool + }{ + { + name: "From fixture location", + rootPath: fixturePath, + want: turbopath.RelativeUnixPath("../../../"), + wantErr: false, + }, + { + name: "Traverse out of git repo", + rootPath: fixturePath.UntypedJoin("..", "..", "..", ".."), + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getTraversePath(tt.rootPath) + if (err != nil) != tt.wantErr { + t.Errorf("getTraversePath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("getTraversePath() = %v, want %v", got, tt.want) + } + }) + } +} + +func requireGitCmd(t *testing.T, repoRoot turbopath.AbsoluteSystemPath, args ...string) { + t.Helper() + cmd := exec.Command("git", args...) + cmd.Dir = repoRoot.ToString() + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("git commit failed: %v %v", err, string(out)) + } +} + +func TestGetPackageDeps(t *testing.T) { + // Directory structure: + // <root>/ + // new-root-file <- new file not added to git + // my-pkg/ + // committed-file + // deleted-file + // uncommitted-file <- new file not added to git + // dir/ + // nested-file + + repoRoot := fs.AbsoluteSystemPathFromUpstream(t.TempDir()) + myPkgDir := repoRoot.UntypedJoin("my-pkg") + + // create the dir first + err := myPkgDir.MkdirAll(0775) + assert.NilError(t, err, "CreateDir") + + // create file 1 + committedFilePath := myPkgDir.UntypedJoin("committed-file") + err = committedFilePath.WriteFile([]byte("committed bytes"), 0644) + assert.NilError(t, err, "WriteFile") + + // create file 2 + deletedFilePath := myPkgDir.UntypedJoin("deleted-file") + err = deletedFilePath.WriteFile([]byte("delete-me"), 0644) + assert.NilError(t, err, "WriteFile") + + // create file 3 + nestedPath := myPkgDir.UntypedJoin("dir", "nested-file") + assert.NilError(t, nestedPath.EnsureDir(), "EnsureDir") + assert.NilError(t, nestedPath.WriteFile([]byte("nested"), 0644), "WriteFile") + + // create a package.json + packageJSONPath := myPkgDir.UntypedJoin("package.json") + err = packageJSONPath.WriteFile([]byte("{}"), 0644) + assert.NilError(t, err, "WriteFile") + + // set up git repo and commit all + requireGitCmd(t, repoRoot, "init", ".") + requireGitCmd(t, repoRoot, "config", "--local", "user.name", "test") + requireGitCmd(t, repoRoot, "config", "--local", "user.email", "test@example.com") + requireGitCmd(t, repoRoot, "add", ".") + requireGitCmd(t, repoRoot, "commit", "-m", "foo") + + // remove a file + err = deletedFilePath.Remove() + assert.NilError(t, err, "Remove") + + // create another untracked file in git + uncommittedFilePath := myPkgDir.UntypedJoin("uncommitted-file") + err = uncommittedFilePath.WriteFile([]byte("uncommitted bytes"), 0644) + assert.NilError(t, err, "WriteFile") + + // create an untracked file in git up a level + rootFilePath := repoRoot.UntypedJoin("new-root-file") + err = rootFilePath.WriteFile([]byte("new-root bytes"), 0644) + assert.NilError(t, err, "WriteFile") + + tests := []struct { + opts *PackageDepsOptions + expected map[turbopath.AnchoredUnixPath]string + }{ + // base case. when inputs aren't specified, all files hashes are computed + { + opts: &PackageDepsOptions{ + PackagePath: "my-pkg", + }, + expected: map[turbopath.AnchoredUnixPath]string{ + "committed-file": "3a29e62ea9ba15c4a4009d1f605d391cdd262033", + "uncommitted-file": "4e56ad89387e6379e4e91ddfe9872cf6a72c9976", + "package.json": "9e26dfeeb6e641a33dae4961196235bdb965b21b", + "dir/nested-file": "bfe53d766e64d78f80050b73cd1c88095bc70abb", + }, + }, + // with inputs, only the specified inputs are hashed + { + opts: &PackageDepsOptions{ + PackagePath: "my-pkg", + InputPatterns: []string{"uncommitted-file"}, + }, + expected: map[turbopath.AnchoredUnixPath]string{ + "package.json": "9e26dfeeb6e641a33dae4961196235bdb965b21b", + "uncommitted-file": "4e56ad89387e6379e4e91ddfe9872cf6a72c9976", + }, + }, + // inputs with glob pattern also works + { + opts: &PackageDepsOptions{ + PackagePath: "my-pkg", + InputPatterns: []string{"**/*-file"}, + }, + expected: map[turbopath.AnchoredUnixPath]string{ + "committed-file": "3a29e62ea9ba15c4a4009d1f605d391cdd262033", + "uncommitted-file": "4e56ad89387e6379e4e91ddfe9872cf6a72c9976", + "package.json": "9e26dfeeb6e641a33dae4961196235bdb965b21b", + "dir/nested-file": "bfe53d766e64d78f80050b73cd1c88095bc70abb", + }, + }, + // inputs with traversal work + { + opts: &PackageDepsOptions{ + PackagePath: "my-pkg", + InputPatterns: []string{"../**/*-file"}, + }, + expected: map[turbopath.AnchoredUnixPath]string{ + "../new-root-file": "8906ddcdd634706188bd8ef1c98ac07b9be3425e", + "committed-file": "3a29e62ea9ba15c4a4009d1f605d391cdd262033", + "uncommitted-file": "4e56ad89387e6379e4e91ddfe9872cf6a72c9976", + "package.json": "9e26dfeeb6e641a33dae4961196235bdb965b21b", + "dir/nested-file": "bfe53d766e64d78f80050b73cd1c88095bc70abb", + }, + }, + // inputs with another glob pattern works + { + opts: &PackageDepsOptions{ + PackagePath: "my-pkg", + InputPatterns: []string{"**/{uncommitted,committed}-file"}, + }, + expected: map[turbopath.AnchoredUnixPath]string{ + "committed-file": "3a29e62ea9ba15c4a4009d1f605d391cdd262033", + "package.json": "9e26dfeeb6e641a33dae4961196235bdb965b21b", + "uncommitted-file": "4e56ad89387e6379e4e91ddfe9872cf6a72c9976", + }, + }, + // inputs with another glob pattern + traversal work + { + opts: &PackageDepsOptions{ + PackagePath: "my-pkg", + InputPatterns: []string{"../**/{new-root,uncommitted,committed}-file"}, + }, + expected: map[turbopath.AnchoredUnixPath]string{ + "../new-root-file": "8906ddcdd634706188bd8ef1c98ac07b9be3425e", + "committed-file": "3a29e62ea9ba15c4a4009d1f605d391cdd262033", + "package.json": "9e26dfeeb6e641a33dae4961196235bdb965b21b", + "uncommitted-file": "4e56ad89387e6379e4e91ddfe9872cf6a72c9976", + }, + }, + } + for _, tt := range tests { + got, err := GetPackageDeps(repoRoot, tt.opts) + if err != nil { + t.Errorf("GetPackageDeps got error %v", err) + continue + } + assert.DeepEqual(t, got, tt.expected) + } +} + +func Test_memoizedGetTraversePath(t *testing.T) { + fixturePath := getFixture(1) + + gotOne, _ := memoizedGetTraversePath(fixturePath) + gotTwo, _ := memoizedGetTraversePath(fixturePath) + + assert.Check(t, gotOne == gotTwo, "The strings are identical.") +} |
