package goverter import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "sort" "strings" "testing" "github.com/jmattheis/goverter/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" ) var ( UpdateScenario = os.Getenv("UPDATE_SCENARIO") == "true" SkipVersionDependent = os.Getenv("SKIP_VERSION_DEPENDENT") == "true" NoParallel = os.Getenv("NO_PARALLEL") == "true" ) func TestScenario(t *testing.T) { rootDir := getCurrentPath() scenarioDir := filepath.Join(rootDir, "scenario") workDir := filepath.Join(rootDir, "execution") scenarioFiles, err := os.ReadDir(scenarioDir) require.NoError(t, err) require.NoError(t, clearDir(workDir)) for _, file := range scenarioFiles { require.False(t, file.IsDir(), "should not be a directory") file := file testName := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) t.Run(testName, func(t *testing.T) { if !NoParallel { t.Parallel() } testWorkDir := filepath.Join(workDir, testName) require.NoError(t, os.MkdirAll(testWorkDir, 0o755)) require.NoError(t, clearDir(testWorkDir)) scenarioFilePath := filepath.Join(scenarioDir, file.Name()) scenarioFileBytes, err := os.ReadFile(scenarioFilePath) require.NoError(t, err) scenario := Scenario{} err = yaml.Unmarshal(scenarioFileBytes, &scenario) require.NoError(t, err) if SkipVersionDependent && scenario.VersionDependent { t.SkipNow() return } err = os.WriteFile(filepath.Join(testWorkDir, "go.mod"), []byte("module github.com/jmattheis/goverter/execution\ngo 1.18"), 0o644) require.NoError(t, err) for name, content := range scenario.Input { inPath := filepath.Join(testWorkDir, name) err = os.MkdirAll(filepath.Dir(inPath), 0o755) require.NoError(t, err) err = os.WriteFile(filepath.Join(testWorkDir, name), []byte(content), 0o644) require.NoError(t, err) } global := append([]string{"output:package github.com/jmattheis/goverter/execution/generated"}, scenario.Global...) patterns := scenario.Patterns if len(patterns) == 0 { patterns = append(patterns, "github.com/jmattheis/goverter/execution") } files, err := generateConvertersRaw( &GenerateConfig{ WorkingDir: testWorkDir, PackagePatterns: patterns, OutputBuildConstraint: scenario.BuildConstraint, BuildTags: "goverter", Global: config.RawLines{ Lines: global, Location: "scenario global", }, }) actualOutputFiles := toOutputFiles(testWorkDir, files) if UpdateScenario { if err != nil { scenario.Success = []*OutputFile{} scenario.Error = replaceAbsolutePath(testWorkDir, fmt.Sprint(err)) } else { scenario.Success = toOutputFiles(testWorkDir, files) scenario.Error = "" } newBytes, err := yaml.Marshal(&scenario) if assert.NoError(t, err) { os.WriteFile(scenarioFilePath, newBytes, 0o644) } } if scenario.Error != "" { require.Error(t, err) require.Equal(t, scenario.Error, replaceAbsolutePath(testWorkDir, fmt.Sprint(err))) return } require.NoError(t, err) require.NotEmpty(t, scenario.Success, "scenario.Success may not be empty") require.Equal(t, scenario.Success, actualOutputFiles) err = writeFiles(files) require.NoError(t, err) require.NoError(t, compile(testWorkDir), "generated converter doesn't build") }) } } func replaceAbsolutePath(curPath, body string) string { return strings.ReplaceAll(body, curPath, "@workdir") } func compile(dir string) error { cmd := exec.Command("go", "build", "./...") cmd.Dir = dir _, err := cmd.Output() if err != nil { if exit, ok := err.(*exec.ExitError); ok { return fmt.Errorf("Process exited with %d:\n%s", exit.ExitCode(), string(exit.Stderr)) } } return err } func toOutputFiles(execDir string, files map[string][]byte) []*OutputFile { output := []*OutputFile{} for fileName, content := range files { rel, err := filepath.Rel(execDir, fileName) if err != nil { panic("could not create relpath") } output = append(output, &OutputFile{Name: rel, Content: string(content)}) } sort.Slice(output, func(i, j int) bool { return output[i].Name < output[j].Name }) return output } type Scenario struct { VersionDependent bool `yaml:"version_dependent,omitempty"` Input map[string]string `yaml:"input"` Global []string `yaml:"global,omitempty"` BuildConstraint string `yaml:"build_constraint,omitempty"` Patterns []string `yaml:"patterns,omitempty"` Success []*OutputFile `yaml:"success,omitempty"` Error string `yaml:"error,omitempty"` } type OutputFile struct { Name string Content string } func (f *OutputFile) MarshalYAML() (interface{}, error) { return map[string]string{f.Name: f.Content}, nil } func (f *OutputFile) UnmarshalYAML(value *yaml.Node) error { v := map[string]string{} err := value.Decode(&v) for name, content := range v { f.Name = name f.Content = content } return err } func getCurrentPath() string { _, filename, _, _ := runtime.Caller(1) return filepath.Dir(filename) } func clearDir(dir string) error { files, err := filepath.Glob(filepath.Join(dir, "*")) if err != nil { return err } for _, file := range files { err = os.RemoveAll(file) if err != nil { return err } } return nil }