// Copyright (c) The OpenTofu Authors // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2023 HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package command import ( "bytes" "fmt" "os" "path/filepath" "sort" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/opentofu/opentofu/internal/command/workdir" ) func TestFmt_TestFiles(t *testing.T) { const inSuffix = "_in.tftest.hcl" const outSuffix = "_out.tftest.hcl" const gotSuffix = "_got.tftest.hcl" entries, err := os.ReadDir("testdata/tftest-fmt") if err != nil { t.Fatal(err) } tmpDir, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatal(err) } for _, info := range entries { if info.IsDir() { continue } filename := info.Name() if !strings.HasSuffix(filename, inSuffix) { continue } testName := filename[:len(filename)-len(inSuffix)] t.Run(testName, func(t *testing.T) { inFile := filepath.Join("testdata", "tftest-fmt", testName+inSuffix) wantFile := filepath.Join("testdata", "tftest-fmt", testName+outSuffix) gotFile := filepath.Join(tmpDir, testName+gotSuffix) input, err := os.ReadFile(inFile) if err != nil { t.Fatal(err) } want, err := os.ReadFile(wantFile) if err != nil { t.Fatal(err) } err = os.WriteFile(gotFile, input, 0700) if err != nil { t.Fatal(err) } view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{gotFile} code := c.Run(args) output := done(t) if code != 0 { t.Fatalf("fmt command was unsuccessful:\n%s", output.Stderr()) } got, err := os.ReadFile(gotFile) if err != nil { t.Fatal(err) } if diff := cmp.Diff(string(want), string(got)); diff != "" { t.Errorf("wrong result\n%s", diff) } }) } } func TestFmt(t *testing.T) { const inSuffix = "_in.tf" const outSuffix = "_out.tf" const gotSuffix = "_got.tf" entries, err := os.ReadDir("testdata/fmt") if err != nil { t.Fatal(err) } tmpDir, err := filepath.EvalSymlinks(t.TempDir()) if err != nil { t.Fatal(err) } for _, info := range entries { if info.IsDir() { continue } filename := info.Name() if !strings.HasSuffix(filename, inSuffix) { continue } testName := filename[:len(filename)-len(inSuffix)] t.Run(testName, func(t *testing.T) { inFile := filepath.Join("testdata", "fmt", testName+inSuffix) wantFile := filepath.Join("testdata", "fmt", testName+outSuffix) gotFile := filepath.Join(tmpDir, testName+gotSuffix) input, err := os.ReadFile(inFile) if err != nil { t.Fatal(err) } want, err := os.ReadFile(wantFile) if err != nil { t.Fatal(err) } err = os.WriteFile(gotFile, input, 0700) if err != nil { t.Fatal(err) } view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{gotFile} code := c.Run(args) output := done(t) if code != 0 { t.Fatalf("fmt command was unsuccessful:\n%s", output.Stderr()) } got, err := os.ReadFile(gotFile) if err != nil { t.Fatal(err) } if diff := cmp.Diff(string(want), string(got)); diff != "" { t.Errorf("wrong result\n%s", diff) } }) } } func TestFmt_nonexist(t *testing.T) { tempDir := fmtFixtureWriteDir(t) view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } missingDir := filepath.Join(tempDir, "doesnotexist") args := []string{missingDir} code := c.Run(args) output := done(t) if code != 2 { t.Fatalf("wrong exit code. got %d. errors: \n%s", code, output.Stderr()) } expected := "No file or directory at" if actual := output.Stderr(); !strings.Contains(actual, expected) { t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected) } } func TestFmt_syntaxError(t *testing.T) { tempDir := testTempDirRealpath(t) invalidSrc := ` a = 1 + ` err := os.WriteFile(filepath.Join(tempDir, "invalid.tf"), []byte(invalidSrc), 0644) if err != nil { t.Fatal(err) } view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{tempDir} code := c.Run(args) output := done(t) if code != 2 { t.Fatalf("wrong exit code. got %d. errors: \n%s", code, output.Stderr()) } expected := "Invalid expression" if actual := output.Stderr(); !strings.Contains(actual, expected) { t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected) } } func TestFmt_snippetInError(t *testing.T) { tempDir := testTempDirRealpath(t) backendSrc := `terraform {backend "s3" {}}` err := os.WriteFile(filepath.Join(tempDir, "backend.tf"), []byte(backendSrc), 0644) if err != nil { t.Fatal(err) } view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{"-no-color", tempDir} code := c.Run(args) output := done(t) if code != 2 { t.Fatalf("wrong exit code. got %d. errors: \n%s", code, output.Stderr()) } substrings := []string{ "Argument definition required", "line 1, in terraform", `1: terraform {backend "s3" {}}`, } for _, substring := range substrings { if actual := output.Stderr(); !strings.Contains(actual, substring) { t.Errorf("expected:\n%s\n\nto include: %q", actual, substring) } } } func TestFmt_manyArgs(t *testing.T) { tempDir := fmtFixtureWriteDir(t) // Add a second file secondSrc := `locals { x = 1 }` err := os.WriteFile(filepath.Join(tempDir, "second.tf"), []byte(secondSrc), 0644) if err != nil { t.Fatal(err) } view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{ filepath.Join(tempDir, "main.tf"), filepath.Join(tempDir, "second.tf"), } code := c.Run(args) output := done(t) if code != 0 { t.Fatalf("wrong exit code. got %d. errors: \n%s", code, output.Stderr()) } got, err := filepath.Abs(strings.TrimSpace(output.Stdout())) if err != nil { t.Fatal(err) } want := filepath.Join(tempDir, fmtFixture.filename) if got != want { t.Fatalf("wrong output\ngot: %s\nwant: %s", got, want) } } func TestFmt_workingDirectory(t *testing.T) { tempDir := fmtFixtureWriteDir(t) t.Chdir(tempDir) view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{} code := c.Run(args) output := done(t) if code != 0 { t.Fatalf("wrong exit code. got %d. errors: \n%s", code, output.Stderr()) } stdout := strings.Split(strings.TrimSpace(output.Stdout()), "\n") // Consistent order sort.Strings(stdout) for i, expected := range []string{fmtFixture.filename, fmtFixture.altFilename} { actual := stdout[i] if actual != expected { t.Fatalf("got: %q\nexpected: %q", actual, expected) } } } func TestFmt_directoryArg(t *testing.T) { tempDir := fmtFixtureWriteDir(t) view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{tempDir} code := c.Run(args) output := done(t) if code != 0 { t.Fatalf("wrong exit code. got %d. errors: \n%s", code, output.Stderr()) } stdout := strings.Split(strings.TrimSpace(output.Stdout()), "\n") // Consistent order sort.Strings(stdout) for i, check := range []string{fmtFixture.filename, fmtFixture.altFilename} { got, err := filepath.Abs(stdout[i]) if err != nil { t.Fatal(err) } want := filepath.Join(tempDir, check) if got != want { t.Fatalf("wrong output\ngot: %s\nwant: %s", got, want) } } } func TestFmt_fileArg(t *testing.T) { tempDir := fmtFixtureWriteDir(t) view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{filepath.Join(tempDir, fmtFixture.filename)} code := c.Run(args) output := done(t) if code != 0 { t.Fatalf("wrong exit code. got %d. errors: \n%s", code, output.Stderr()) } got, err := filepath.Abs(strings.TrimSpace(output.Stdout())) if err != nil { t.Fatal(err) } want := filepath.Join(tempDir, fmtFixture.filename) if got != want { t.Fatalf("wrong output\ngot: %s\nwant: %s", got, want) } } func TestFmt_stdinArg(t *testing.T) { input := new(bytes.Buffer) input.Write(fmtFixture.input) view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, input: input, } args := []string{"-"} code := c.Run(args) output := done(t) if code != 0 { t.Fatalf("wrong exit code. got %d. errors: \n%s", code, output.Stderr()) } expected := fmtFixture.golden if actual := []byte(output.Stdout()); !bytes.Equal(actual, expected) { t.Fatalf("got: %q\nexpected: %q", actual, expected) } } func TestFmt_nonDefaultOptions(t *testing.T) { tempDir := fmtFixtureWriteDir(t) view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{ "-list=false", "-write=false", "-diff", tempDir, } code := c.Run(args) output := done(t) if code != 0 { t.Fatalf("wrong exit code. got %d. errors: \n%s", code, output.Stderr()) } expected := fmt.Sprintf("-%s+%s", fmtFixture.input, fmtFixture.golden) if actual := output.Stdout(); !strings.Contains(actual, expected) { t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected) } } func TestFmt_check(t *testing.T) { tempDir := fmtFixtureWriteDir(t) view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, } args := []string{ "-check", tempDir, } code := c.Run(args) output := done(t) if code != 3 { t.Fatalf("wrong exit code. expected 3") } // Given that we give relative paths back to the user, normalize this temp // dir so that we're comparing against a relative-ized (normalized) path tempDir = c.Meta.WorkingDir.NormalizePath(tempDir) if actual := output.Stdout(); !strings.Contains(actual, tempDir) { t.Fatalf("expected:\n%s\n\nto include: %q", actual, tempDir) } } func TestFmt_checkStdin(t *testing.T) { input := new(bytes.Buffer) input.Write(fmtFixture.input) view, done := testView(t) c := &FmtCommand{ Meta: Meta{ WorkingDir: workdir.NewDir("."), testingOverrides: metaOverridesForProvider(testProvider()), View: view, }, input: input, } args := []string{ "-check", "-", } code := c.Run(args) output := done(t) if code != 3 { t.Fatalf("wrong exit code. expected 3, got %d", code) } stdout := output.Stdout() if len(stdout) > 0 { t.Fatalf("expected no output, got: %q", stdout) } } var fmtFixture = struct { filename string altFilename string input, golden []byte }{ "main.tf", "main.tofu", []byte(` foo = "bar" `), []byte(`foo = "bar" `), } func fmtFixtureWriteDir(t *testing.T) string { dir := testTempDirRealpath(t) err := os.WriteFile(filepath.Join(dir, fmtFixture.filename), fmtFixture.input, 0600) if err != nil { t.Fatal(err) } err = os.WriteFile(filepath.Join(dir, fmtFixture.altFilename), fmtFixture.input, 0600) if err != nil { t.Fatal(err) } return dir }