package devbox import ( "encoding/json" "fmt" "os" "path/filepath" "testing" "github.com/bmatcuk/doublestar/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.jetpack.io/devbox/planner/plansdk" ) func TestDevbox(t *testing.T) { t.Setenv("TMPDIR", "/tmp") testPaths, err := doublestar.FilepathGlob("./testdata/**/devbox.json") assert.NoError(t, err, "Reading testdata/ should not fail") examplePaths, err := doublestar.FilepathGlob("./examples/**/devbox.json") assert.NoError(t, err, "Reading examples/ should not fail") testPaths = append(testPaths, examplePaths...) assert.Greater(t, len(testPaths), 0, "testdata/ and examples/ should contain at least 1 test") for _, testPath := range testPaths { testShell(t, testPath) testBuild(t, testPath) } } func testShell(t *testing.T, testPath string) { currentDir, err := os.Getwd() require.New(t).NoError(err) baseDir := filepath.Dir(testPath) testName := fmt.Sprintf("%s_shell_plan", baseDir) t.Run(testName, func(t *testing.T) { assert := assert.New(t) shellPlanFile := filepath.Join(baseDir, "shell_plan.json") hasShellPlanFile := fileExists(shellPlanFile) box, err := Open(baseDir, os.Stdout) assert.NoErrorf(err, "%s should be a valid devbox project", baseDir) // Just for tests, we make srcDir be a relative path so that the paths in plan.json // of various test cases have relative paths. Absolute paths are a no-go because they'd // be of the form `/Users/savil/...`, which are not generalized and cannot be checked in. box.srcDir, err = filepath.Rel(currentDir, box.srcDir) assert.NoErrorf(err, "expect to construct relative path from %s relative to base %s", box.srcDir, currentDir) shellPlan, err := box.ShellPlan() assert.NoError(err, "devbox shell plan should not fail") err = box.generateShellFiles() assert.NoError(err, "devbox generate should not fail") if !hasShellPlanFile { assert.NotEmpty(shellPlan.DevPackages, "the plan should have dev packages") return } data, err := os.ReadFile(shellPlanFile) assert.NoError(err, "shell_plan.json should be readable") expected := &plansdk.ShellPlan{} err = json.Unmarshal(data, &expected) assert.NoError(err, "plan.json should parse correctly") assertShellPlansMatch(t, expected, shellPlan) }) } func testBuild(t *testing.T, testPath string) { currentDir, err := os.Getwd() require.New(t).NoError(err) baseDir := filepath.Dir(testPath) testName := fmt.Sprintf("%s_build_plan", baseDir) t.Run(testName, func(t *testing.T) { assert := assert.New(t) buildPlanFile := filepath.Join(baseDir, "build_plan.json") hasBuildPlanFile := fileExists(buildPlanFile) box, err := Open(baseDir, os.Stdout) assert.NoErrorf(err, "%s should be a valid devbox project", baseDir) // Just for tests, we make srcDir be a relative path so that the paths in plan.json // of various test cases have relative paths. Absolute paths are a no-go because they'd // be of the form `/Users/savil/...`, which are not generalized and cannot be checked in. box.srcDir, err = filepath.Rel(currentDir, box.srcDir) assert.NoErrorf(err, "expect to construct relative path from %s relative to base %s", box.srcDir, currentDir) buildPlan, err := box.BuildPlan() buildErrorExpectedFile := filepath.Join(baseDir, "build_error_expected") hasBuildErrorExpectedFile := fileExists(buildErrorExpectedFile) if hasBuildErrorExpectedFile { assert.NotNil(err) // Since build error is expected, skip the rest of the test return } assert.NoError(err, "devbox plan should not fail") err = box.generateBuildFiles() assert.NoError(err, "devbox generate should not fail") if !hasBuildPlanFile { assert.NotEmpty(buildPlan.DevPackages, "the plan should have dev packages") return } data, err := os.ReadFile(buildPlanFile) assert.NoError(err, "plan.json should be readable") expected := &plansdk.BuildPlan{} err = json.Unmarshal(data, &expected) assert.NoError(err, "plan.json should parse correctly") assertBuildPlansMatch(t, expected, buildPlan) }) } func assertShellPlansMatch(t *testing.T, expected *plansdk.ShellPlan, actual *plansdk.ShellPlan) { assert := assert.New(t) assert.ElementsMatch(expected.DevPackages, actual.DevPackages, "DevPackages should match") assert.ElementsMatch(expected.NixOverlays, actual.NixOverlays, "NixOverlays should match") } func assertBuildPlansMatch(t *testing.T, expected *plansdk.BuildPlan, actual *plansdk.BuildPlan) { assert := assert.New(t) assert.ElementsMatch(expected.DevPackages, actual.DevPackages, "DevPackages should match") assert.ElementsMatch(expected.RuntimePackages, actual.RuntimePackages, "RuntimePackages should match") assert.Equal(expected.InstallStage.GetCommand(), actual.InstallStage.GetCommand(), "Install stage should match") assert.Equal(expected.BuildStage.GetCommand(), actual.BuildStage.GetCommand(), "Build stage should match") assert.Equal(expected.StartStage.GetCommand(), actual.StartStage.GetCommand(), "Start stage should match") // Check that input files are the same for all stages. // Depending on where the test command is invoked, the input file paths can be different. // We will compare the file name only. assert.ElementsMatch( expected.InstallStage.GetInputFiles(), getFileNames(actual.InstallStage.GetInputFiles()), "InstallStage.InputFiles should match", ) assert.ElementsMatch( expected.BuildStage.GetInputFiles(), getFileNames(actual.BuildStage.GetInputFiles()), "BuildStage.InputFiles should match", ) assert.ElementsMatch( expected.StartStage.GetInputFiles(), actual.StartStage.GetInputFiles(), "StartStage.InputFiles should match", ) assert.ElementsMatch(expected.Definitions, actual.Definitions, "Definitions should match") } func fileExists(path string) bool { _, err := os.Stat(path) return err == nil } func getFileNames(paths []string) []string { names := []string{} for _, path := range paths { if path == "." { names = append(names, path) } else { names = append(names, filepath.Base(path)) } } return names }