1// +build !windows 2// TODO(jen20): These need fixing on Windows but fmt is not used right now 3// and red CI is making it harder to process other bugs, so ignore until 4// we get around to fixing them. 5 6package fmtcmd 7 8import ( 9 "bytes" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "reflect" 15 "regexp" 16 "sort" 17 "syscall" 18 "testing" 19 20 "github.com/hashicorp/hcl/testhelper" 21) 22 23var fixtureExtensions = []string{"hcl"} 24 25func init() { 26 sort.Sort(ByFilename(fixtures)) 27} 28 29func TestIsValidFile(t *testing.T) { 30 const fixtureDir = "./test-fixtures" 31 32 cases := []struct { 33 Path string 34 Expected bool 35 }{ 36 {"good.hcl", true}, 37 {".hidden.ignore", false}, 38 {"file.ignore", false}, 39 {"dir.ignore", false}, 40 } 41 42 for _, tc := range cases { 43 file, err := os.Stat(filepath.Join(fixtureDir, tc.Path)) 44 if err != nil { 45 t.Errorf("unexpected error: %s", err) 46 } 47 48 if res := isValidFile(file, fixtureExtensions); res != tc.Expected { 49 t.Errorf("want: %t, got: %t", tc.Expected, res) 50 } 51 } 52} 53 54func TestRunMultiplePaths(t *testing.T) { 55 path1, err := renderFixtures("") 56 if err != nil { 57 t.Errorf("unexpected error: %s", err) 58 } 59 defer os.RemoveAll(path1) 60 path2, err := renderFixtures("") 61 if err != nil { 62 t.Errorf("unexpected error: %s", err) 63 } 64 defer os.RemoveAll(path2) 65 66 var expectedOut bytes.Buffer 67 for _, path := range []string{path1, path2} { 68 for _, fixture := range fixtures { 69 if !bytes.Equal(fixture.golden, fixture.input) { 70 expectedOut.WriteString(filepath.Join(path, fixture.filename) + "\n") 71 } 72 } 73 } 74 75 _, stdout := mockIO() 76 err = Run( 77 []string{path1, path2}, 78 fixtureExtensions, 79 nil, stdout, 80 Options{ 81 List: true, 82 }, 83 ) 84 85 if err != nil { 86 t.Errorf("unexpected error: %s", err) 87 } 88 if stdout.String() != expectedOut.String() { 89 t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) 90 } 91} 92 93func TestRunSubDirectories(t *testing.T) { 94 pathParent, err := ioutil.TempDir("", "") 95 if err != nil { 96 t.Errorf("unexpected error: %s", err) 97 } 98 defer os.RemoveAll(pathParent) 99 100 path1, err := renderFixtures(pathParent) 101 if err != nil { 102 t.Errorf("unexpected error: %s", err) 103 } 104 path2, err := renderFixtures(pathParent) 105 if err != nil { 106 t.Errorf("unexpected error: %s", err) 107 } 108 109 paths := []string{path1, path2} 110 sort.Strings(paths) 111 112 var expectedOut bytes.Buffer 113 for _, path := range paths { 114 for _, fixture := range fixtures { 115 if !bytes.Equal(fixture.golden, fixture.input) { 116 expectedOut.WriteString(filepath.Join(path, fixture.filename) + "\n") 117 } 118 } 119 } 120 121 _, stdout := mockIO() 122 err = Run( 123 []string{pathParent}, 124 fixtureExtensions, 125 nil, stdout, 126 Options{ 127 List: true, 128 }, 129 ) 130 131 if err != nil { 132 t.Errorf("unexpected error: %s", err) 133 } 134 if stdout.String() != expectedOut.String() { 135 t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) 136 } 137} 138 139func TestRunStdin(t *testing.T) { 140 var expectedOut bytes.Buffer 141 for i, fixture := range fixtures { 142 if i != 0 { 143 expectedOut.WriteString("\n") 144 } 145 expectedOut.Write(fixture.golden) 146 } 147 148 stdin, stdout := mockIO() 149 for _, fixture := range fixtures { 150 stdin.Write(fixture.input) 151 } 152 153 err := Run( 154 []string{}, 155 fixtureExtensions, 156 stdin, stdout, 157 Options{}, 158 ) 159 160 if err != nil { 161 t.Errorf("unexpected error: %s", err) 162 } 163 if !bytes.Equal(stdout.Bytes(), expectedOut.Bytes()) { 164 t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) 165 } 166} 167 168func TestRunStdinAndWrite(t *testing.T) { 169 var expectedOut = []byte{} 170 171 stdin, stdout := mockIO() 172 stdin.WriteString("") 173 err := Run( 174 []string{}, []string{}, 175 stdin, stdout, 176 Options{ 177 Write: true, 178 }, 179 ) 180 181 if err != ErrWriteStdin { 182 t.Errorf("error want:\n%s\ngot:\n%s", ErrWriteStdin, err) 183 } 184 if !bytes.Equal(stdout.Bytes(), expectedOut) { 185 t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut, stdout) 186 } 187} 188 189func TestRunFileError(t *testing.T) { 190 path, err := ioutil.TempDir("", "") 191 if err != nil { 192 t.Errorf("unexpected error: %s", err) 193 } 194 defer os.RemoveAll(path) 195 filename := filepath.Join(path, "unreadable.hcl") 196 197 var expectedError = &os.PathError{ 198 Op: "open", 199 Path: filename, 200 Err: syscall.EACCES, 201 } 202 203 err = ioutil.WriteFile(filename, []byte{}, 0000) 204 if err != nil { 205 t.Errorf("unexpected error: %s", err) 206 } 207 208 _, stdout := mockIO() 209 err = Run( 210 []string{path}, 211 fixtureExtensions, 212 nil, stdout, 213 Options{}, 214 ) 215 216 if !reflect.DeepEqual(err, expectedError) { 217 t.Errorf("error want: %#v, got: %#v", expectedError, err) 218 } 219} 220 221func TestRunNoOptions(t *testing.T) { 222 path, err := renderFixtures("") 223 if err != nil { 224 t.Errorf("unexpected error: %s", err) 225 } 226 defer os.RemoveAll(path) 227 228 var expectedOut bytes.Buffer 229 for _, fixture := range fixtures { 230 expectedOut.Write(fixture.golden) 231 } 232 233 _, stdout := mockIO() 234 err = Run( 235 []string{path}, 236 fixtureExtensions, 237 nil, stdout, 238 Options{}, 239 ) 240 241 if err != nil { 242 t.Errorf("unexpected error: %s", err) 243 } 244 if stdout.String() != expectedOut.String() { 245 t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) 246 } 247} 248 249func TestRunList(t *testing.T) { 250 path, err := renderFixtures("") 251 if err != nil { 252 t.Errorf("unexpected error: %s", err) 253 } 254 defer os.RemoveAll(path) 255 256 var expectedOut bytes.Buffer 257 for _, fixture := range fixtures { 258 if !bytes.Equal(fixture.golden, fixture.input) { 259 expectedOut.WriteString(fmt.Sprintln(filepath.Join(path, fixture.filename))) 260 } 261 } 262 263 _, stdout := mockIO() 264 err = Run( 265 []string{path}, 266 fixtureExtensions, 267 nil, stdout, 268 Options{ 269 List: true, 270 }, 271 ) 272 273 if err != nil { 274 t.Errorf("unexpected error: %s", err) 275 } 276 if stdout.String() != expectedOut.String() { 277 t.Errorf("stdout want:\n%s\ngot:\n%s", expectedOut.String(), stdout.String()) 278 } 279} 280 281func TestRunWrite(t *testing.T) { 282 path, err := renderFixtures("") 283 if err != nil { 284 t.Errorf("unexpected error: %s", err) 285 } 286 defer os.RemoveAll(path) 287 288 _, stdout := mockIO() 289 err = Run( 290 []string{path}, 291 fixtureExtensions, 292 nil, stdout, 293 Options{ 294 Write: true, 295 }, 296 ) 297 298 if err != nil { 299 t.Errorf("unexpected error: %s", err) 300 } 301 for _, fixture := range fixtures { 302 res, err := ioutil.ReadFile(filepath.Join(path, fixture.filename)) 303 if err != nil { 304 t.Errorf("unexpected error: %s", err) 305 } 306 if !bytes.Equal(res, fixture.golden) { 307 t.Errorf("file %q contents want:\n%s\ngot:\n%s", fixture.filename, fixture.golden, res) 308 } 309 } 310} 311 312func TestRunDiff(t *testing.T) { 313 path, err := renderFixtures("") 314 if err != nil { 315 t.Errorf("unexpected error: %s", err) 316 } 317 defer os.RemoveAll(path) 318 319 var expectedOut bytes.Buffer 320 for _, fixture := range fixtures { 321 if len(fixture.diff) > 0 { 322 expectedOut.WriteString( 323 regexp.QuoteMeta( 324 fmt.Sprintf("diff a/%s/%s b/%s/%s\n", path, fixture.filename, path, fixture.filename), 325 ), 326 ) 327 // Need to use regex to ignore datetimes in diff. 328 expectedOut.WriteString(`--- .+?\n`) 329 expectedOut.WriteString(`\+\+\+ .+?\n`) 330 expectedOut.WriteString(regexp.QuoteMeta(string(fixture.diff))) 331 } 332 } 333 334 expectedOutString := testhelper.Unix2dos(expectedOut.String()) 335 336 _, stdout := mockIO() 337 err = Run( 338 []string{path}, 339 fixtureExtensions, 340 nil, stdout, 341 Options{ 342 Diff: true, 343 }, 344 ) 345 346 if err != nil { 347 t.Errorf("unexpected error: %s", err) 348 } 349 if !regexp.MustCompile(expectedOutString).Match(stdout.Bytes()) { 350 t.Errorf("stdout want match:\n%s\ngot:\n%q", expectedOutString, stdout) 351 } 352} 353 354func mockIO() (stdin, stdout *bytes.Buffer) { 355 return new(bytes.Buffer), new(bytes.Buffer) 356} 357 358type fixture struct { 359 filename string 360 input, golden, diff []byte 361} 362 363type ByFilename []fixture 364 365func (s ByFilename) Len() int { return len(s) } 366func (s ByFilename) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 367func (s ByFilename) Less(i, j int) bool { return len(s[i].filename) > len(s[j].filename) } 368 369var fixtures = []fixture{ 370 { 371 "noop.hcl", 372 []byte(`resource "aws_security_group" "firewall" { 373 count = 5 374} 375`), 376 []byte(`resource "aws_security_group" "firewall" { 377 count = 5 378} 379`), 380 []byte(``), 381 }, { 382 "align_equals.hcl", 383 []byte(`variable "foo" { 384 default = "bar" 385 description = "bar" 386} 387`), 388 []byte(`variable "foo" { 389 default = "bar" 390 description = "bar" 391} 392`), 393 []byte(`@@ -1,4 +1,4 @@ 394 variable "foo" { 395- default = "bar" 396+ default = "bar" 397 description = "bar" 398 } 399`), 400 }, { 401 "indentation.hcl", 402 []byte(`provider "aws" { 403 access_key = "foo" 404 secret_key = "bar" 405} 406`), 407 []byte(`provider "aws" { 408 access_key = "foo" 409 secret_key = "bar" 410} 411`), 412 []byte(`@@ -1,4 +1,4 @@ 413 provider "aws" { 414- access_key = "foo" 415- secret_key = "bar" 416+ access_key = "foo" 417+ secret_key = "bar" 418 } 419`), 420 }, 421} 422 423// parent can be an empty string, in which case the system's default 424// temporary directory will be used. 425func renderFixtures(parent string) (path string, err error) { 426 path, err = ioutil.TempDir(parent, "") 427 if err != nil { 428 return "", err 429 } 430 431 for _, fixture := range fixtures { 432 err = ioutil.WriteFile(filepath.Join(path, fixture.filename), []byte(fixture.input), 0644) 433 if err != nil { 434 os.RemoveAll(path) 435 return "", err 436 } 437 } 438 439 return path, nil 440} 441