aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/scope/scope_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'cli/internal/scope/scope_test.go')
-rw-r--r--cli/internal/scope/scope_test.go550
1 files changed, 0 insertions, 550 deletions
diff --git a/cli/internal/scope/scope_test.go b/cli/internal/scope/scope_test.go
deleted file mode 100644
index 216984d..0000000
--- a/cli/internal/scope/scope_test.go
+++ /dev/null
@@ -1,550 +0,0 @@
-package scope
-
-import (
- "fmt"
- "io"
- "os"
- "path/filepath"
- "reflect"
- "testing"
-
- "github.com/hashicorp/go-hclog"
- "github.com/pyr-sh/dag"
- "github.com/vercel/turbo/cli/internal/context"
- "github.com/vercel/turbo/cli/internal/fs"
- "github.com/vercel/turbo/cli/internal/lockfile"
- "github.com/vercel/turbo/cli/internal/packagemanager"
- "github.com/vercel/turbo/cli/internal/turbopath"
- "github.com/vercel/turbo/cli/internal/ui"
- "github.com/vercel/turbo/cli/internal/util"
- "github.com/vercel/turbo/cli/internal/workspace"
-)
-
-type mockSCM struct {
- changed []string
- contents map[string][]byte
-}
-
-func (m *mockSCM) ChangedFiles(_fromCommit string, _toCommit string, _relativeTo string) ([]string, error) {
- return m.changed, nil
-}
-
-func (m *mockSCM) PreviousContent(fromCommit string, filePath string) ([]byte, error) {
- contents, ok := m.contents[filePath]
- if !ok {
- return nil, fmt.Errorf("No contents found")
- }
- return contents, nil
-}
-
-type mockLockfile struct {
- globalChange bool
- versions map[string]string
- allDeps map[string]map[string]string
-}
-
-func (m *mockLockfile) ResolvePackage(workspacePath turbopath.AnchoredUnixPath, name string, version string) (lockfile.Package, error) {
- resolvedVersion, ok := m.versions[name]
- if ok {
- key := fmt.Sprintf("%s%s", name, version)
- return lockfile.Package{Key: key, Version: resolvedVersion, Found: true}, nil
- }
- return lockfile.Package{Found: false}, nil
-}
-
-func (m *mockLockfile) AllDependencies(key string) (map[string]string, bool) {
- deps, ok := m.allDeps[key]
- return deps, ok
-}
-
-func (m *mockLockfile) Encode(w io.Writer) error {
- return nil
-}
-
-func (m *mockLockfile) GlobalChange(other lockfile.Lockfile) bool {
- return m.globalChange || (other != nil && other.(*mockLockfile).globalChange)
-}
-
-func (m *mockLockfile) Patches() []turbopath.AnchoredUnixPath {
- return nil
-}
-
-func (m *mockLockfile) Subgraph(workspaces []turbopath.AnchoredSystemPath, packages []string) (lockfile.Lockfile, error) {
- return nil, nil
-}
-
-var _ (lockfile.Lockfile) = (*mockLockfile)(nil)
-
-func TestResolvePackages(t *testing.T) {
- cwd, err := os.Getwd()
- if err != nil {
- t.Fatalf("cwd: %v", err)
- }
- root, err := fs.GetCwd(cwd)
- if err != nil {
- t.Fatalf("cwd: %v", err)
- }
- tui := ui.Default()
- logger := hclog.Default()
- // Dependency graph:
- //
- // app0 -
- // \
- // app1 -> libA
- // \
- // > libB -> libD
- // /
- // app2 <
- // \
- // > libC
- // /
- // app2-a <
- //
- // Filesystem layout:
- //
- // app/
- // app0
- // app1
- // app2
- // app2-a
- // libs/
- // libA
- // libB
- // libC
- // libD
- graph := dag.AcyclicGraph{}
- graph.Add("app0")
- graph.Add("app1")
- graph.Add("app2")
- graph.Add("app2-a")
- graph.Add("libA")
- graph.Add("libB")
- graph.Add("libC")
- graph.Add("libD")
- graph.Connect(dag.BasicEdge("libA", "libB"))
- graph.Connect(dag.BasicEdge("libB", "libD"))
- graph.Connect(dag.BasicEdge("app0", "libA"))
- graph.Connect(dag.BasicEdge("app1", "libA"))
- graph.Connect(dag.BasicEdge("app2", "libB"))
- graph.Connect(dag.BasicEdge("app2", "libC"))
- graph.Connect(dag.BasicEdge("app2-a", "libC"))
- workspaceInfos := workspace.Catalog{
- PackageJSONs: map[string]*fs.PackageJSON{
- "//": {
- Dir: turbopath.AnchoredSystemPath("").ToSystemPath(),
- UnresolvedExternalDeps: map[string]string{"global": "2"},
- TransitiveDeps: []lockfile.Package{{Key: "global2", Version: "2", Found: true}},
- },
- "app0": {
- Dir: turbopath.AnchoredUnixPath("app/app0").ToSystemPath(),
- Name: "app0",
- UnresolvedExternalDeps: map[string]string{"app0-dep": "2"},
- TransitiveDeps: []lockfile.Package{
- {Key: "app0-dep2", Version: "2", Found: true},
- {Key: "app0-util2", Version: "2", Found: true},
- },
- },
- "app1": {
- Dir: turbopath.AnchoredUnixPath("app/app1").ToSystemPath(),
- Name: "app1",
- },
- "app2": {
- Dir: turbopath.AnchoredUnixPath("app/app2").ToSystemPath(),
- Name: "app2",
- },
- "app2-a": {
- Dir: turbopath.AnchoredUnixPath("app/app2-a").ToSystemPath(),
- Name: "app2-a",
- },
- "libA": {
- Dir: turbopath.AnchoredUnixPath("libs/libA").ToSystemPath(),
- Name: "libA",
- },
- "libB": {
- Dir: turbopath.AnchoredUnixPath("libs/libB").ToSystemPath(),
- Name: "libB",
- UnresolvedExternalDeps: map[string]string{"external": "1"},
- TransitiveDeps: []lockfile.Package{
- {Key: "external-dep-a1", Version: "1", Found: true},
- {Key: "external-dep-b1", Version: "1", Found: true},
- {Key: "external1", Version: "1", Found: true},
- },
- },
- "libC": {
- Dir: turbopath.AnchoredUnixPath("libs/libC").ToSystemPath(),
- Name: "libC",
- },
- "libD": {
- Dir: turbopath.AnchoredUnixPath("libs/libD").ToSystemPath(),
- Name: "libD",
- },
- },
- }
- packageNames := []string{}
- for name := range workspaceInfos.PackageJSONs {
- packageNames = append(packageNames, name)
- }
-
- // global -> globalDep
- // app0-dep -> app0-dep :)
-
- makeLockfile := func(f func(*mockLockfile)) *mockLockfile {
- l := mockLockfile{
- globalChange: false,
- versions: map[string]string{
- "global": "2",
- "app0-dep": "2",
- "app0-util": "2",
- "external": "1",
- "external-dep-a": "1",
- "external-dep-b": "1",
- },
- allDeps: map[string]map[string]string{
- "global2": map[string]string{},
- "app0-dep2": map[string]string{
- "app0-util": "2",
- },
- "app0-util2": map[string]string{},
- "external1": map[string]string{
- "external-dep-a": "1",
- "external-dep-b": "1",
- },
- "external-dep-a1": map[string]string{},
- "external-dep-b1": map[string]string{},
- },
- }
- if f != nil {
- f(&l)
- }
- return &l
- }
-
- testCases := []struct {
- name string
- changed []string
- expected []string
- expectAllPackages bool
- scope []string
- since string
- ignore string
- globalDeps []string
- includeDependencies bool
- includeDependents bool
- lockfile string
- currLockfile *mockLockfile
- prevLockfile *mockLockfile
- inferPkgPath string
- }{
- {
- name: "Just scope and dependencies",
- changed: []string{},
- includeDependencies: true,
- scope: []string{"app2"},
- expected: []string{"app2", "libB", "libC", "libD"},
- },
- {
- name: "Only turbo.json changed",
- changed: []string{"turbo.json"},
- expected: []string{"//", "app0", "app1", "app2", "app2-a", "libA", "libB", "libC", "libD"},
- since: "dummy",
- includeDependencies: true,
- },
- {
- name: "Only root package.json changed",
- changed: []string{"package.json"},
- expected: []string{"//", "app0", "app1", "app2", "app2-a", "libA", "libB", "libC", "libD"},
- since: "dummy",
- includeDependencies: true,
- },
- {
- name: "Only package-lock.json changed",
- changed: []string{"package-lock.json"},
- expected: []string{"//", "app0", "app1", "app2", "app2-a", "libA", "libB", "libC", "libD"},
- since: "dummy",
- includeDependencies: true,
- lockfile: "package-lock.json",
- },
- {
- name: "Only yarn.lock changed",
- changed: []string{"yarn.lock"},
- expected: []string{"//", "app0", "app1", "app2", "app2-a", "libA", "libB", "libC", "libD"},
- since: "dummy",
- includeDependencies: true,
- lockfile: "yarn.lock",
- },
- {
- name: "Only pnpm-lock.yaml changed",
- changed: []string{"pnpm-lock.yaml"},
- expected: []string{"//", "app0", "app1", "app2", "app2-a", "libA", "libB", "libC", "libD"},
- since: "dummy",
- includeDependencies: true,
- lockfile: "pnpm-lock.yaml",
- },
- {
- name: "One package changed",
- changed: []string{"libs/libB/src/index.ts"},
- expected: []string{"libB"},
- since: "dummy",
- },
- {
- name: "One package manifest changed",
- changed: []string{"libs/libB/package.json"},
- expected: []string{"libB"},
- since: "dummy",
- },
- {
- name: "An ignored package changed",
- changed: []string{"libs/libB/src/index.ts"},
- expected: []string{},
- since: "dummy",
- ignore: "libs/libB/**/*.ts",
- },
- {
- // nothing in scope depends on the change
- name: "unrelated library changed",
- changed: []string{"libs/libC/src/index.ts"},
- expected: []string{},
- since: "dummy",
- scope: []string{"app1"},
- includeDependencies: true, // scope implies include-dependencies
- },
- {
- // a dependent lib changed, scope implies include-dependencies,
- // so all deps of app1 get built
- name: "dependency of scope changed",
- changed: []string{"libs/libA/src/index.ts"},
- expected: []string{"libA", "libB", "libD", "app1"},
- since: "dummy",
- scope: []string{"app1"},
- includeDependencies: true, // scope implies include-dependencies
- },
- {
- // a dependent lib changed, user explicitly asked to not build dependencies.
- // Since the package matching the scope had a changed dependency, we run it.
- // We don't include its dependencies because the user asked for no dependencies.
- // note: this is not yet supported by the CLI, as you cannot specify --include-dependencies=false
- name: "dependency of scope changed, user asked to not include depedencies",
- changed: []string{"libs/libA/src/index.ts"},
- expected: []string{"app1"},
- since: "dummy",
- scope: []string{"app1"},
- includeDependencies: false,
- },
- {
- // a nested dependent lib changed, user explicitly asked to not build dependencies
- // note: this is not yet supported by the CLI, as you cannot specify --include-dependencies=false
- name: "nested dependency of scope changed, user asked to not include dependencies",
- changed: []string{"libs/libB/src/index.ts"},
- expected: []string{"app1"},
- since: "dummy",
- scope: []string{"app1"},
- includeDependencies: false,
- },
- {
- name: "global dependency changed, even though it was ignored, forcing a build of everything",
- changed: []string{"libs/libB/src/index.ts"},
- expected: []string{"//", "app0", "app1", "app2", "app2-a", "libA", "libB", "libC", "libD"},
- since: "dummy",
- ignore: "libs/libB/**/*.ts",
- globalDeps: []string{"libs/**/*.ts"},
- },
- {
- name: "an app changed, user asked for dependencies to build",
- changed: []string{"app/app2/src/index.ts"},
- since: "dummy",
- includeDependencies: true,
- expected: []string{"app2", "libB", "libC", "libD"},
- },
- {
- name: "a library changed, user asked for dependents to be built",
- changed: []string{"libs/libB"},
- since: "dummy",
- includeDependents: true,
- expected: []string{"app0", "app1", "app2", "libA", "libB"},
- },
- {
- // no changes, no base to compare against, defaults to everything
- name: "no changes or scope specified, build everything",
- since: "",
- expected: []string{"//", "app0", "app1", "app2", "app2-a", "libA", "libB", "libC", "libD"},
- expectAllPackages: true,
- },
- {
- // a dependent library changed, no deps beyond the scope are build
- // "libB" is still built because it is a dependent within the scope, but libB's dependents
- // are skipped
- name: "a dependent library changed, build up to scope",
- changed: []string{"libs/libD/src/index.ts"},
- since: "dummy",
- scope: []string{"libB"},
- expected: []string{"libB", "libD"},
- includeDependencies: true, // scope implies include-dependencies
- },
- {
- name: "library change, no scope",
- changed: []string{"libs/libA/src/index.ts"},
- expected: []string{"libA", "app0", "app1"},
- includeDependents: true,
- since: "dummy",
- },
- {
- // make sure multiple apps with the same prefix are handled separately.
- // prevents this issue: https://github.com/vercel/turbo/issues/1528
- name: "Two apps with an overlapping prefix changed",
- changed: []string{"app/app2/src/index.js", "app/app2-a/src/index.js"},
- expected: []string{"app2", "app2-a"},
- since: "dummy",
- },
- {
- name: "Global lockfile change invalidates all packages",
- changed: []string{"dummy.lock"},
- expected: []string{"//", "app0", "app1", "app2", "app2-a", "libA", "libB", "libC", "libD"},
- lockfile: "dummy.lock",
- currLockfile: makeLockfile(nil),
- prevLockfile: makeLockfile(func(ml *mockLockfile) {
- ml.globalChange = true
- }),
- since: "dummy",
- },
- {
- name: "Dependency of workspace root change invalidates all packages",
- changed: []string{"dummy.lock"},
- expected: []string{"//", "app0", "app1", "app2", "app2-a", "libA", "libB", "libC", "libD"},
- lockfile: "dummy.lock",
- currLockfile: makeLockfile(nil),
- prevLockfile: makeLockfile(func(ml *mockLockfile) {
- ml.versions["global"] = "3"
- ml.allDeps["global3"] = map[string]string{}
- }),
- since: "dummy",
- },
- {
- name: "Version change invalidates package",
- changed: []string{"dummy.lock"},
- expected: []string{"//", "app0"},
- lockfile: "dummy.lock",
- currLockfile: makeLockfile(nil),
- prevLockfile: makeLockfile(func(ml *mockLockfile) {
- ml.versions["app0-util"] = "3"
- ml.allDeps["app0-dep2"] = map[string]string{"app0-util": "3"}
- ml.allDeps["app0-util3"] = map[string]string{}
- }),
- since: "dummy",
- },
- {
- name: "Transitive dep invalidates package",
- changed: []string{"dummy.lock"},
- expected: []string{"//", "libB"},
- lockfile: "dummy.lock",
- currLockfile: makeLockfile(nil),
- prevLockfile: makeLockfile(func(ml *mockLockfile) {
- ml.versions["external-dep-a"] = "2"
- ml.allDeps["external1"] = map[string]string{"external-dep-a": "2", "external-dep-b": "1"}
- ml.allDeps["external-dep-a2"] = map[string]string{}
- }),
- since: "dummy",
- },
- {
- name: "Transitive dep invalidates package and dependents",
- changed: []string{"dummy.lock"},
- expected: []string{"//", "app0", "app1", "app2", "libA", "libB"},
- lockfile: "dummy.lock",
- includeDependents: true,
- currLockfile: makeLockfile(nil),
- prevLockfile: makeLockfile(func(ml *mockLockfile) {
- ml.versions["external-dep-a"] = "2"
- ml.allDeps["external1"] = map[string]string{"external-dep-a": "2", "external-dep-b": "1"}
- ml.allDeps["external-dep-a2"] = map[string]string{}
- }),
- since: "dummy",
- },
- {
- name: "Infer app2 from directory",
- inferPkgPath: "app/app2",
- expected: []string{"app2"},
- },
- {
- name: "Infer app2 from a subdirectory",
- inferPkgPath: "app/app2/src",
- expected: []string{"app2"},
- },
- {
- name: "Infer from a directory with no packages",
- inferPkgPath: "wrong",
- expected: []string{},
- },
- {
- name: "Infer from a parent directory",
- inferPkgPath: "app",
- expected: []string{"app0", "app1", "app2", "app2-a"},
- },
- {
- name: "library change, no scope, inferred libs",
- changed: []string{"libs/libA/src/index.ts"},
- expected: []string{"libA"},
- since: "dummy",
- inferPkgPath: "libs",
- },
- {
- name: "library change, no scope, inferred app",
- changed: []string{"libs/libA/src/index.ts"},
- expected: []string{},
- since: "dummy",
- inferPkgPath: "app",
- },
- }
- for i, tc := range testCases {
- t.Run(fmt.Sprintf("test #%v %v", i, tc.name), func(t *testing.T) {
- // Convert test data to system separators.
- systemSeparatorChanged := make([]string, len(tc.changed))
- for index, path := range tc.changed {
- systemSeparatorChanged[index] = filepath.FromSlash(path)
- }
- scm := &mockSCM{
- changed: systemSeparatorChanged,
- contents: make(map[string][]byte, len(systemSeparatorChanged)),
- }
- for _, path := range systemSeparatorChanged {
- scm.contents[path] = nil
- }
- readLockfile := func(_rootPackageJSON *fs.PackageJSON, content []byte) (lockfile.Lockfile, error) {
- return tc.prevLockfile, nil
- }
- pkgInferenceRoot, err := resolvePackageInferencePath(tc.inferPkgPath)
- if err != nil {
- t.Errorf("bad inference path (%v): %v", tc.inferPkgPath, err)
- }
- pkgs, isAllPackages, err := ResolvePackages(&Opts{
- LegacyFilter: LegacyFilter{
- Entrypoints: tc.scope,
- Since: tc.since,
- IncludeDependencies: tc.includeDependencies,
- SkipDependents: !tc.includeDependents,
- },
- IgnorePatterns: []string{tc.ignore},
- GlobalDepPatterns: tc.globalDeps,
- PackageInferenceRoot: pkgInferenceRoot,
- }, root, scm, &context.Context{
- WorkspaceInfos: workspaceInfos,
- WorkspaceNames: packageNames,
- PackageManager: &packagemanager.PackageManager{Lockfile: tc.lockfile, UnmarshalLockfile: readLockfile},
- WorkspaceGraph: graph,
- RootNode: "root",
- Lockfile: tc.currLockfile,
- }, tui, logger)
- if err != nil {
- t.Errorf("expected no error, got %v", err)
- }
- expected := make(util.Set)
- for _, pkg := range tc.expected {
- expected.Add(pkg)
- }
- if !reflect.DeepEqual(pkgs, expected) {
- t.Errorf("ResolvePackages got %v, want %v", pkgs, expected)
- }
- if isAllPackages != tc.expectAllPackages {
- t.Errorf("isAllPackages got %v, want %v", isAllPackages, tc.expectAllPackages)
- }
- })
- }
-}