// +build !windows // TODO(jen20): These need fixing on Windows but fmt is not used right now // and red CI is making it harder to process other bugs, so ignore until // we get around to fixing them. package fmtcmd import ( "bytes" "fmt" "io/ioutil" "os" "path/filepath" "reflect" "regexp" "sort" "syscall" "testing" "github.com/hashicorp/hcl/testhelper" ) var fixtureExtensions = []string{"hcl"} func init() { sort.Sort(ByFilename(fixtures)) } func TestIsValidFile(t *testing.T) { const fixtureDir = "./test-fixtures" cases := []struct { Path string Expected bool }{ {"good.hcl", true}, {".hidden.ignore", false}, {"file.ignore", false}, {"dir.ignore", false}, } for _, tc := range cases { file, err := os.Stat(filepath.Join(fixtureDir, tc.Path)) if err != nil { t.Errorf("unexpected error: %s", err) } if res := isValidFile(file, fixtureExtensions); res != tc.Expected { t.Errorf("want: %t, got: %t", tc.Expected, res) } } } func TestRunMultiplePaths(t *testing.T) { path1, err := renderFixtures("") if err != nil { t.Errorf("unexpected error: %s", err) } defer os.RemoveAll(path1) path2, err := renderFixtures("") if err != nil { t.Errorf("unexpected error: %s", err) } defer os.RemoveAll(path2) var expectedOut bytes.Buffer for _, path := range []string{path1, path2} { for _, fixture := range fixtures { if !bytes.Equal(fixture.golden, fixture.input) { expectedOut.WriteString(filepath.Join(path, fixture.filename) + "\n") } } } _, stdout := mockIO() err = Run( []string{path1, path2}, fixtureExtensions, nil, stdout, Options{ List: true, }, ) if err != nil { t.Errorf("unexpected error: %s", err) } if stdout.String() != expectedOut.String() { t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) } } func TestRunSubDirectories(t *testing.T) { pathParent, err := ioutil.TempDir("", "") if err != nil { t.Errorf("unexpected error: %s", err) } defer os.RemoveAll(pathParent) path1, err := renderFixtures(pathParent) if err != nil { t.Errorf("unexpected error: %s", err) } path2, err := renderFixtures(pathParent) if err != nil { t.Errorf("unexpected error: %s", err) } paths := []string{path1, path2} sort.Strings(paths) var expectedOut bytes.Buffer for _, path := range paths { for _, fixture := range fixtures { if !bytes.Equal(fixture.golden, fixture.input) { expectedOut.WriteString(filepath.Join(path, fixture.filename) + "\n") } } } _, stdout := mockIO() err = Run( []string{pathParent}, fixtureExtensions, nil, stdout, Options{ List: true, }, ) if err != nil { t.Errorf("unexpected error: %s", err) } if stdout.String() != expectedOut.String() { t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) } } func TestRunStdin(t *testing.T) { var expectedOut bytes.Buffer for i, fixture := range fixtures { if i != 0 { expectedOut.WriteString("\n") } expectedOut.Write(fixture.golden) } stdin, stdout := mockIO() for _, fixture := range fixtures { stdin.Write(fixture.input) } err := Run( []string{}, fixtureExtensions, stdin, stdout, Options{}, ) if err != nil { t.Errorf("unexpected error: %s", err) } if !bytes.Equal(stdout.Bytes(), expectedOut.Bytes()) { t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) } } func TestRunStdinAndWrite(t *testing.T) { var expectedOut = []byte{} stdin, stdout := mockIO() stdin.WriteString("") err := Run( []string{}, []string{}, stdin, stdout, Options{ Write: true, }, ) if err != ErrWriteStdin { t.Errorf("error want:\n%s\ngot:\n%s", ErrWriteStdin, err) } if !bytes.Equal(stdout.Bytes(), expectedOut) { t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut, stdout) } } func TestRunFileError(t *testing.T) { path, err := ioutil.TempDir("", "") if err != nil { t.Errorf("unexpected error: %s", err) } defer os.RemoveAll(path) filename := filepath.Join(path, "unreadable.hcl") var expectedError = &os.PathError{ Op: "open", Path: filename, Err: syscall.EACCES, } err = ioutil.WriteFile(filename, []byte{}, 0000) if err != nil { t.Errorf("unexpected error: %s", err) } _, stdout := mockIO() err = Run( []string{path}, fixtureExtensions, nil, stdout, Options{}, ) if !reflect.DeepEqual(err, expectedError) { t.Errorf("error want: %#v, got: %#v", expectedError, err) } } func TestRunNoOptions(t *testing.T) { path, err := renderFixtures("") if err != nil { t.Errorf("unexpected error: %s", err) } defer os.RemoveAll(path) var expectedOut bytes.Buffer for _, fixture := range fixtures { expectedOut.Write(fixture.golden) } _, stdout := mockIO() err = Run( []string{path}, fixtureExtensions, nil, stdout, Options{}, ) if err != nil { t.Errorf("unexpected error: %s", err) } if stdout.String() != expectedOut.String() { t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) } } func TestRunList(t *testing.T) { path, err := renderFixtures("") if err != nil { t.Errorf("unexpected error: %s", err) } defer os.RemoveAll(path) var expectedOut bytes.Buffer for _, fixture := range fixtures { if !bytes.Equal(fixture.golden, fixture.input) { expectedOut.WriteString(fmt.Sprintln(filepath.Join(path, fixture.filename))) } } _, stdout := mockIO() err = Run( []string{path}, fixtureExtensions, nil, stdout, Options{ List: true, }, ) if err != nil { t.Errorf("unexpected error: %s", err) } if stdout.String() != expectedOut.String() { t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) } } func TestRunWrite(t *testing.T) { path, err := renderFixtures("") if err != nil { t.Errorf("unexpected error: %s", err) } defer os.RemoveAll(path) _, stdout := mockIO() err = Run( []string{path}, fixtureExtensions, nil, stdout, Options{ Write: true, }, ) if err != nil { t.Errorf("unexpected error: %s", err) } for _, fixture := range fixtures { res, err := ioutil.ReadFile(filepath.Join(path, fixture.filename)) if err != nil { t.Errorf("unexpected error: %s", err) } if !bytes.Equal(res, fixture.golden) { t.Errorf("file %q contents want:\n%s\ngot:\n%s", fixture.filename, fixture.golden, res) } } } func TestRunDiff(t *testing.T) { path, err := renderFixtures("") if err != nil { t.Errorf("unexpected error: %s", err) } defer os.RemoveAll(path) var expectedOut bytes.Buffer for _, fixture := range fixtures { if len(fixture.diff) > 0 { expectedOut.WriteString( regexp.QuoteMeta( fmt.Sprintf("diff a/%s/%s b/%s/%s\n", path, fixture.filename, path, fixture.filename), ), ) // Need to use regex to ignore datetimes in diff. expectedOut.WriteString(`--- .+?\n`) expectedOut.WriteString(`\+\+\+ .+?\n`) expectedOut.WriteString(regexp.QuoteMeta(string(fixture.diff))) } } expectedOutString := testhelper.Unix2dos(expectedOut.String()) _, stdout := mockIO() err = Run( []string{path}, fixtureExtensions, nil, stdout, Options{ Diff: true, }, ) if err != nil { t.Errorf("unexpected error: %s", err) } if !regexp.MustCompile(expectedOutString).Match(stdout.Bytes()) { t.Errorf("stdout want match:\n%s\ngot:\n%q", expectedOutString, stdout) } } func mockIO() (stdin, stdout *bytes.Buffer) { return new(bytes.Buffer), new(bytes.Buffer) } type fixture struct { filename string input, golden, diff []byte } type ByFilename []fixture func (s ByFilename) Len() int { return len(s) } func (s ByFilename) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ByFilename) Less(i, j int) bool { return len(s[i].filename) > len(s[j].filename) } var fixtures = []fixture{ { "noop.hcl", []byte(`resource "aws_security_group" "firewall" { count = 5 } `), []byte(`resource "aws_security_group" "firewall" { count = 5 } `), []byte(``), }, { "align_equals.hcl", []byte(`variable "foo" { default = "bar" description = "bar" } `), []byte(`variable "foo" { default = "bar" description = "bar" } `), []byte(`@@ -1,4 +1,4 @@ variable "foo" { - default = "bar" + default = "bar" description = "bar" } `), }, { "indentation.hcl", []byte(`provider "aws" { access_key = "foo" secret_key = "bar" } `), []byte(`provider "aws" { access_key = "foo" secret_key = "bar" } `), []byte(`@@ -1,4 +1,4 @@ provider "aws" { - access_key = "foo" - secret_key = "bar" + access_key = "foo" + secret_key = "bar" } `), }, } // parent can be an empty string, in which case the system's default // temporary directory will be used. func renderFixtures(parent string) (path string, err error) { path, err = ioutil.TempDir(parent, "") if err != nil { return "", err } for _, fixture := range fixtures { err = ioutil.WriteFile(filepath.Join(path, fixture.filename), []byte(fixture.input), 0644) if err != nil { os.RemoveAll(path) return "", err } } return path, nil }