diff --git a/integration/bundle/init_default_python_test.go b/integration/bundle/init_default_python_test.go index 25f1f77482..25a84e3f29 100644 --- a/integration/bundle/init_default_python_test.go +++ b/integration/bundle/init_default_python_test.go @@ -3,6 +3,7 @@ package bundle_test import ( "encoding/json" "os" + "os/exec" "path/filepath" "testing" @@ -60,11 +61,19 @@ func testDefaultPython(t *testing.T, pythonVersion string) { testcli.PrepareReplacementsUser(t, replacements, *user) } - tmpDir1, pythonExe := pythontest.RequirePythonVENV(t, ctx, pythonVersion, true) + tmpDir := t.TempDir() + + opts := pythontest.VenvOpts{ + PythonVersion: pythonVersion, + Dir: tmpDir, + } + + pythontest.RequireActivatedPythonEnv(t, ctx, &opts) extras, ok := extraInstalls[pythonVersion] if ok { - args := append([]string{"pip", "install", "--python", pythonExe}, extras...) - testutil.RunCommand(t, "uv", args...) + args := append([]string{"pip", "install", "--python", opts.PythonExe}, extras...) + cmd := exec.Command("uv", args...) + require.NoError(t, cmd.Run()) } projectName := "project_name_" + uniqueProjectId @@ -77,7 +86,7 @@ func testDefaultPython(t *testing.T, pythonVersion string) { } b, err := json.Marshal(initConfig) require.NoError(t, err) - err = os.WriteFile(filepath.Join(tmpDir1, "config.json"), b, 0o644) + err = os.WriteFile(filepath.Join(tmpDir, "config.json"), b, 0o644) require.NoError(t, err) testcli.RequireOutput(t, ctx, []string{"bundle", "init", "default-python", "--config-file", "config.json"}, "testdata/default_python/bundle_init.txt") diff --git a/internal/testcli/golden.go b/internal/testcli/golden.go index e22790cd34..210d10e409 100644 --- a/internal/testcli/golden.go +++ b/internal/testcli/golden.go @@ -9,17 +9,19 @@ import ( "runtime" "slices" "strings" + "testing" "github.com/databricks/cli/internal/testutil" + "github.com/databricks/cli/libs/golden" "github.com/databricks/cli/libs/iamutil" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/elliotchance/orderedmap/v3" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/wI2L/jsondiff" ) +var OverwriteMode = os.Getenv("TESTS_OUTPUT") == "OVERWRITE" + func ReadFile(t testutil.TestingT, ctx context.Context, filename string) string { data, err := os.ReadFile(filename) if os.IsNotExist(err) { @@ -30,12 +32,6 @@ func ReadFile(t testutil.TestingT, ctx context.Context, filename string) string return NormalizeNewlines(string(data)) } -func WriteFile(t testutil.TestingT, ctx context.Context, filename, data string) { - t.Logf("Overwriting %s", filename) - err := os.WriteFile(filename, []byte(data), 0o644) - require.NoError(t, err) -} - func captureOutput(t testutil.TestingT, ctx context.Context, args []string) string { t.Logf("run args: [%s]", strings.Join(args, ", ")) r := NewRunner(t, ctx, args...) @@ -45,20 +41,10 @@ func captureOutput(t testutil.TestingT, ctx context.Context, args []string) stri return ReplaceOutput(t, ctx, out) } -func assertEqualTexts(t testutil.TestingT, filename1, filename2, expected, out string) { - if len(out) < 1000 && len(expected) < 1000 { - // This shows full strings + diff which could be useful when debugging newlines - assert.Equal(t, expected, out) - } else { - // only show diff for large texts - diff := testutil.Diff(filename1, filename2, expected, out) - t.Errorf("Diff:\n" + diff) - } -} - -func logDiff(t testutil.TestingT, filename1, filename2, expected, out string) { - diff := testutil.Diff(filename1, filename2, expected, out) - t.Logf("Diff:\n" + diff) +func WriteFile(t testutil.TestingT, filename, data string) { + t.Logf("Overwriting %s", filename) + err := os.WriteFile(filename, []byte(data), 0o644) + require.NoError(t, err) } func RequireOutput(t testutil.TestingT, ctx context.Context, args []string, expectedFilename string) { @@ -71,10 +57,10 @@ func RequireOutput(t testutil.TestingT, ctx context.Context, args []string, expe if out != expected { actual := fmt.Sprintf("Output from %v", args) - assertEqualTexts(t, expectedFilename, actual, expected, out) + golden.AssertEqualTexts(t, expectedFilename, actual, expected, out) - if os.Getenv("TESTS_OUTPUT") == "OVERWRITE" { - WriteFile(t, ctx, expectedPath, out) + if OverwriteMode { + WriteFile(t, expectedPath, out) } } } @@ -88,46 +74,13 @@ func RequireOutputJQ(t testutil.TestingT, ctx context.Context, args []string, ex out := captureOutput(t, ctx, args) if out != expected { - patch, err := jsondiff.CompareJSON([]byte(expected), []byte(out)) actual := fmt.Sprintf("Output from %v", args) - if err != nil { - t.Logf("CompareJSON error for %s vs %s: %s (fallback to textual comparison)", args, expectedFilename, err) - assertEqualTexts(t, expectedFilename, actual, expected, out) - } else { - logDiff(t, expectedFilename, actual, expected, out) - ignoredDiffs := []string{} - erroredDiffs := []string{} - for _, op := range patch { - if matchesPrefixes(ignorePaths, op.Path) { - ignoredDiffs = append(ignoredDiffs, fmt.Sprintf("%7s %s %v", op.Type, op.Path, op.Value)) - } else { - erroredDiffs = append(erroredDiffs, fmt.Sprintf("%7s %s %v", op.Type, op.Path, op.Value)) - } - } - if len(ignoredDiffs) > 0 { - t.Logf("Ignored differences between %s and %s:\n ==> %s", expectedFilename, args, strings.Join(ignoredDiffs, "\n ==> ")) - } - if len(erroredDiffs) > 0 { - t.Errorf("Unexpected differences between %s and %s:\n ==> %s", expectedFilename, args, strings.Join(erroredDiffs, "\n ==> ")) - } - } + golden.AssertEqualJSONs(t.(*testing.T), expectedFilename, actual, expected, out, ignorePaths) - if os.Getenv("TESTS_OUTPUT") == "OVERWRITE" { - WriteFile(t, ctx, filepath.Join(dir, expectedFilename), out) - } - } -} - -func matchesPrefixes(prefixes []string, path string) bool { - for _, p := range prefixes { - if p == path { - return true - } - if strings.HasPrefix(path, p+"/") { - return true + if OverwriteMode { + WriteFile(t, expectedPath, out) } } - return false } var ( diff --git a/internal/testcli/golden_test.go b/internal/testcli/golden_test.go index 7aba982806..215bf33d32 100644 --- a/internal/testcli/golden_test.go +++ b/internal/testcli/golden_test.go @@ -11,10 +11,3 @@ func TestSort(t *testing.T) { stableSortReverseLength(input) assert.Equal(t, []string{"bc", "cd", "a"}, input) } - -func TestMatchesPrefixes(t *testing.T) { - assert.False(t, matchesPrefixes([]string{}, "")) - assert.False(t, matchesPrefixes([]string{"/hello", "/hello/world"}, "")) - assert.True(t, matchesPrefixes([]string{"/hello", "/a/b"}, "/hello")) - assert.True(t, matchesPrefixes([]string{"/hello", "/a/b"}, "/a/b/c")) -} diff --git a/internal/testutil/diff.go b/internal/testutil/diff.go deleted file mode 100644 index 1a2c043bd7..0000000000 --- a/internal/testutil/diff.go +++ /dev/null @@ -1,14 +0,0 @@ -package testutil - -import ( - "fmt" - - "github.com/hexops/gotextdiff" - "github.com/hexops/gotextdiff/myers" - "github.com/hexops/gotextdiff/span" -) - -func Diff(filename1, filename2, s1, s2 string) string { - edits := myers.ComputeEdits(span.URIFromPath(filename1), s1, s2) - return fmt.Sprint(gotextdiff.ToUnified(filename1, filename2, s1, edits)) -} diff --git a/libs/golden/golden.go b/libs/golden/golden.go new file mode 100644 index 0000000000..6c72bb36db --- /dev/null +++ b/libs/golden/golden.go @@ -0,0 +1,68 @@ +package golden + +import ( + "fmt" + "strings" + "testing" + + "github.com/databricks/cli/internal/testutil" + "github.com/hexops/gotextdiff" + "github.com/hexops/gotextdiff/myers" + "github.com/hexops/gotextdiff/span" + "github.com/stretchr/testify/assert" + "github.com/wI2L/jsondiff" +) + +func UnifiedDiff(filename1, filename2, s1, s2 string) string { + edits := myers.ComputeEdits(span.URIFromPath(filename1), s1, s2) + return fmt.Sprint(gotextdiff.ToUnified(filename1, filename2, s1, edits)) +} + +func AssertEqualTexts(t testutil.TestingT, filename1, filename2, expected, out string) { + if len(out) < 1000 && len(expected) < 1000 { + // This shows full strings + diff which could be useful when debugging newlines + assert.Equal(t, expected, out) + } else { + // only show diff for large texts + diff := UnifiedDiff(filename1, filename2, expected, out) + t.Errorf("Diff:\n" + diff) + } +} + +func AssertEqualJSONs(t *testing.T, expectedName, outName, expected, out string, ignorePaths []string) { + patch, err := jsondiff.CompareJSON([]byte(expected), []byte(out)) + if err != nil { + t.Logf("CompareJSON error for %s vs %s: %s (fallback to textual comparison)", outName, expectedName, err) + AssertEqualTexts(t, expectedName, outName, expected, out) + } else { + diff := UnifiedDiff(expectedName, outName, expected, out) + t.Logf("Diff:\n%s", diff) + ignoredDiffs := []string{} + erroredDiffs := []string{} + for _, op := range patch { + if matchesPrefixes(ignorePaths, op.Path) { + ignoredDiffs = append(ignoredDiffs, fmt.Sprintf("%7s %s %v", op.Type, op.Path, op.Value)) + } else { + erroredDiffs = append(erroredDiffs, fmt.Sprintf("%7s %s %v", op.Type, op.Path, op.Value)) + } + } + if len(ignoredDiffs) > 0 { + t.Logf("Ignored differences between %s and %s:\n ==> %s", expectedName, outName, strings.Join(ignoredDiffs, "\n ==> ")) + } + if len(erroredDiffs) > 0 { + t.Errorf("Unexpected differences between %s and %s:\n ==> %s", expectedName, outName, strings.Join(erroredDiffs, "\n ==> ")) + } + } +} + +func matchesPrefixes(prefixes []string, path string) bool { + for _, p := range prefixes { + if p == path { + return true + } + if strings.HasPrefix(path, p+"/") { + return true + } + } + return false +} diff --git a/libs/golden/golden_test.go b/libs/golden/golden_test.go new file mode 100644 index 0000000000..7f11583cf9 --- /dev/null +++ b/libs/golden/golden_test.go @@ -0,0 +1,20 @@ +package golden + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDiff(t *testing.T) { + assert.Equal(t, "", UnifiedDiff("a", "b", "", "")) + assert.Equal(t, "", UnifiedDiff("a", "b", "abc", "abc")) + assert.Equal(t, "+123", UnifiedDiff("a", "b", "abc", "abc\123")) +} + +func TestMatchesPrefixes(t *testing.T) { + assert.False(t, matchesPrefixes([]string{}, "")) + assert.False(t, matchesPrefixes([]string{"/hello", "/hello/world"}, "")) + assert.True(t, matchesPrefixes([]string{"/hello", "/a/b"}, "/hello")) + assert.True(t, matchesPrefixes([]string{"/hello", "/a/b"}, "/a/b/c")) +}