github.com/jpreese/tflint@v0.19.2-0.20200908152133-b01686250fb6/cmd/cli_test.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 12 "github.com/fatih/color" 13 "github.com/golang/mock/gomock" 14 hcl "github.com/hashicorp/hcl/v2" 15 "github.com/hashicorp/terraform/configs" 16 "github.com/hashicorp/terraform/terraform" 17 "github.com/terraform-linters/tflint/rules" 18 "github.com/terraform-linters/tflint/tflint" 19 ) 20 21 func TestCLIRun__noIssuesFound(t *testing.T) { 22 cases := []struct { 23 Name string 24 Command string 25 LoadErr error 26 Status int 27 Stdout string 28 Stderr string 29 }{ 30 { 31 Name: "print version", 32 Command: "./tflint --version", 33 Status: ExitCodeOK, 34 Stdout: fmt.Sprintf("TFLint version %s", tflint.Version), 35 }, 36 { 37 Name: "print help", 38 Command: "./tflint --help", 39 Status: ExitCodeOK, 40 Stdout: "Application Options:", 41 }, 42 { 43 Name: "no options", 44 Command: "./tflint", 45 Status: ExitCodeOK, 46 Stdout: "", 47 }, 48 { 49 Name: "specify format", 50 Command: "./tflint --format json", 51 Status: ExitCodeOK, 52 Stdout: "[]", 53 }, 54 { 55 Name: "`--force` option", 56 Command: "./tflint --force", 57 Status: ExitCodeOK, 58 Stdout: "", 59 }, 60 { 61 Name: "`--only` option", 62 Command: "./tflint --only aws_instance_invalid_type", 63 Status: ExitCodeOK, 64 Stdout: "", 65 }, 66 { 67 Name: "loading errors are occurred", 68 Command: "./tflint", 69 LoadErr: errors.New("Load error occurred"), 70 Status: ExitCodeError, 71 Stderr: "Load error occurred", 72 }, 73 { 74 Name: "removed `debug` options", 75 Command: "./tflint --debug", 76 Status: ExitCodeError, 77 Stderr: "`debug` option was removed in v0.8.0. Please set `TFLINT_LOG` environment variables instead", 78 }, 79 { 80 Name: "removed `fast` option", 81 Command: "./tflint --fast", 82 Status: ExitCodeError, 83 Stderr: "`fast` option was removed in v0.9.0. The `aws_instance_invalid_ami` rule is already fast enough", 84 }, 85 { 86 Name: "removed `--error-with-issues` option", 87 Command: "./tflint --error-with-issues", 88 Status: ExitCodeError, 89 Stderr: "`error-with-issues` option was removed in v0.9.0. The behavior is now default", 90 }, 91 { 92 Name: "removed `--quiet` option", 93 Command: "./tflint --quiet", 94 Status: ExitCodeError, 95 Stderr: "`quiet` option was removed in v0.11.0. The behavior is now default", 96 }, 97 { 98 Name: "removed `--ignore-rule` option", 99 Command: "./tflint --ignore-rule aws_instance_invalid_type", 100 Status: ExitCodeError, 101 Stderr: "`ignore-rule` option was removed in v0.12.0. Please use `--disable-rule` instead", 102 }, 103 { 104 Name: "invalid options", 105 Command: "./tflint --unknown", 106 Status: ExitCodeError, 107 Stderr: "`unknown` is unknown option. Please run `tflint --help`", 108 }, 109 { 110 Name: "invalid format", 111 Command: "./tflint --format awesome", 112 Status: ExitCodeError, 113 Stderr: "Invalid value `awesome' for option", 114 }, 115 { 116 Name: "invalid rule name", 117 Command: "./tflint --enable-rule nosuchrule", 118 Status: ExitCodeError, 119 Stderr: "Rule not found: nosuchrule", 120 }, 121 { 122 Name: "enable rule which has required configuration", 123 Command: "./tflint --enable-rule aws_resource_missing_tags", 124 Status: ExitCodeError, 125 Stderr: "This rule cannot be enabled with the `--enable-rule` option because it lacks the required configuration", 126 }, 127 } 128 129 ctrl := gomock.NewController(t) 130 defer ctrl.Finish() 131 132 for _, tc := range cases { 133 outStream, errStream := new(bytes.Buffer), new(bytes.Buffer) 134 cli := &CLI{ 135 outStream: outStream, 136 errStream: errStream, 137 testMode: true, 138 } 139 140 loader := tflint.NewMockAbstractLoader(ctrl) 141 loader.EXPECT().LoadConfig(".").Return(configs.NewEmptyConfig(), tc.LoadErr).AnyTimes() 142 loader.EXPECT().Files().Return(map[string]*hcl.File{}, tc.LoadErr).AnyTimes() 143 loader.EXPECT().LoadAnnotations(".").Return(map[string]tflint.Annotations{}, tc.LoadErr).AnyTimes() 144 loader.EXPECT().LoadValuesFiles().Return([]terraform.InputValues{}, tc.LoadErr).AnyTimes() 145 loader.EXPECT().Sources().Return(map[string][]byte{}).AnyTimes() 146 cli.loader = loader 147 148 status := cli.Run(strings.Split(tc.Command, " ")) 149 150 if status != tc.Status { 151 t.Fatalf("Failed `%s`: Expected status is `%d`, but get `%d`", tc.Name, tc.Status, status) 152 } 153 if !strings.Contains(outStream.String(), tc.Stdout) { 154 t.Fatalf("Failed `%s`: Expected to contain `%s` in stdout, but get `%s`", tc.Name, tc.Stdout, outStream.String()) 155 } 156 if tc.Stdout == "" && outStream.String() != "" { 157 t.Fatalf("Failed `%s`: Expected empty in stdout, but get `%s`", tc.Name, outStream.String()) 158 } 159 if !strings.Contains(errStream.String(), tc.Stderr) { 160 t.Fatalf("Failed `%s`: Expected to contain `%s` in stderr, but get `%s`", tc.Name, tc.Stderr, errStream.String()) 161 } 162 if tc.Stderr == "" && errStream.String() != "" { 163 t.Fatalf("Failed `%s`: Expected empty in stderr, but get `%s`", tc.Name, errStream.String()) 164 } 165 } 166 } 167 168 type testRule struct { 169 dir string 170 } 171 type errorRule struct{} 172 173 func (r *testRule) Name() string { 174 return "test_rule" 175 } 176 func (r *errorRule) Name() string { 177 return "error_rule" 178 } 179 180 func (r *testRule) Enabled() bool { 181 return true 182 } 183 func (r *errorRule) Enabled() bool { 184 return true 185 } 186 187 func (r *testRule) Severity() string { 188 return tflint.ERROR 189 } 190 func (r *errorRule) Severity() string { 191 return tflint.ERROR 192 } 193 194 func (r *testRule) Link() string { 195 return "" 196 } 197 func (r *errorRule) Link() string { 198 return "" 199 } 200 201 func (r *testRule) Check(runner *tflint.Runner) error { 202 filename := "test.tf" 203 if r.dir != "" { 204 filename = filepath.Join(r.dir, filename) 205 } 206 207 runner.EmitIssue( 208 r, 209 "This is test error", 210 hcl.Range{ 211 Filename: filename, 212 Start: hcl.Pos{Line: 1}, 213 }, 214 ) 215 return nil 216 } 217 func (r *errorRule) Check(runner *tflint.Runner) error { 218 return errors.New("Check failed") 219 } 220 221 func TestCLIRun__issuesFound(t *testing.T) { 222 cases := []struct { 223 Name string 224 Command string 225 Rule rules.Rule 226 Status int 227 Stdout string 228 Stderr string 229 }{ 230 { 231 Name: "issues found", 232 Command: "./tflint", 233 Rule: &testRule{}, 234 Status: ExitCodeIssuesFound, 235 Stdout: fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")), 236 }, 237 { 238 Name: "`--force` option", 239 Command: "./tflint --force", 240 Rule: &testRule{}, 241 Status: ExitCodeOK, 242 Stdout: fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")), 243 }, 244 { 245 Name: "`--no-color` option", 246 Command: "./tflint --no-color", 247 Rule: &testRule{}, 248 Status: ExitCodeIssuesFound, 249 Stdout: "This is test error (test_rule)", 250 }, 251 { 252 Name: "checking errors are occurred", 253 Command: "./tflint", 254 Rule: &errorRule{}, 255 Status: ExitCodeError, 256 Stderr: "Check failed", 257 }, 258 } 259 260 ctrl := gomock.NewController(t) 261 originalRules := rules.DefaultRules 262 defer func() { 263 rules.DefaultRules = originalRules 264 ctrl.Finish() 265 }() 266 267 for _, tc := range cases { 268 // Mock rules 269 rules.DefaultRules = []rules.Rule{tc.Rule} 270 271 outStream, errStream := new(bytes.Buffer), new(bytes.Buffer) 272 cli := &CLI{ 273 outStream: outStream, 274 errStream: errStream, 275 testMode: true, 276 } 277 278 loader := tflint.NewMockAbstractLoader(ctrl) 279 loader.EXPECT().LoadConfig(".").Return(configs.NewEmptyConfig(), nil).AnyTimes() 280 loader.EXPECT().Files().Return(map[string]*hcl.File{}, nil).AnyTimes() 281 loader.EXPECT().LoadAnnotations(".").Return(map[string]tflint.Annotations{}, nil).AnyTimes() 282 loader.EXPECT().LoadValuesFiles().Return([]terraform.InputValues{}, nil).AnyTimes() 283 loader.EXPECT().Sources().Return(map[string][]byte{}).AnyTimes() 284 cli.loader = loader 285 286 status := cli.Run(strings.Split(tc.Command, " ")) 287 288 if status != tc.Status { 289 t.Fatalf("Failed `%s`: Expected status is `%d`, but get `%d`", tc.Name, tc.Status, status) 290 } 291 if !strings.Contains(outStream.String(), tc.Stdout) { 292 t.Fatalf("Failed `%s`: Expected to contain `%s` in stdout, but get `%s`", tc.Name, tc.Stdout, outStream.String()) 293 } 294 if tc.Stdout == "" && outStream.String() != "" { 295 t.Fatalf("Failed `%s`: Expected empty in stdout, but get `%s`", tc.Name, outStream.String()) 296 } 297 if !strings.Contains(errStream.String(), tc.Stderr) { 298 t.Fatalf("Failed `%s`: Expected to contain `%s` in stderr, but get `%s`", tc.Name, tc.Stderr, errStream.String()) 299 } 300 if tc.Stderr == "" && errStream.String() != "" { 301 t.Fatalf("Failed `%s`: Expected empty in stderr, but get `%s`", tc.Name, errStream.String()) 302 } 303 } 304 } 305 306 func TestCLIRun__withArguments(t *testing.T) { 307 cases := []struct { 308 Name string 309 Command string 310 Dir string 311 Status int 312 Stdout string 313 Stderr string 314 }{ 315 { 316 Name: "no arguments", 317 Command: "./tflint", 318 Dir: ".", 319 Status: ExitCodeIssuesFound, 320 Stdout: fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")), 321 }, 322 { 323 Name: "files arguments", 324 Command: "./tflint template.tf", 325 Dir: ".", 326 Status: ExitCodeOK, 327 Stdout: "", 328 }, 329 { 330 Name: "file not found", 331 Command: "./tflint not_found.tf", 332 Dir: ".", 333 Status: ExitCodeError, 334 Stderr: "Failed to load `not_found.tf`: File not found", 335 }, 336 { 337 Name: "not Terraform configuration", 338 Command: "./tflint README", 339 Dir: ".", 340 Status: ExitCodeError, 341 Stderr: "Failed to load `README`: File is not a target of Terraform", 342 }, 343 { 344 Name: "multiple files", 345 Command: "./tflint template.tf test.tf", 346 Dir: ".", 347 Status: ExitCodeIssuesFound, 348 Stdout: fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")), 349 }, 350 { 351 Name: "directory argument", 352 Command: "./tflint example", 353 Dir: "example", 354 Status: ExitCodeIssuesFound, 355 Stdout: fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")), 356 }, 357 { 358 Name: "file under the directory", 359 Command: fmt.Sprintf("./tflint %s", filepath.Join("example", "test.tf")), 360 Dir: "example", 361 Status: ExitCodeIssuesFound, 362 Stdout: fmt.Sprintf("%s (test_rule)", color.New(color.Bold).Sprint("This is test error")), 363 }, 364 { 365 Name: "multiple directories", 366 Command: "./tflint example ./", 367 Dir: "example", 368 Status: ExitCodeError, 369 Stderr: "Failed to load `example`: Multiple arguments are not allowed when passing a directory", 370 }, 371 { 372 Name: "file and directory", 373 Command: "./tflint template.tf example", 374 Dir: "example", 375 Status: ExitCodeError, 376 Stderr: "Failed to load `example`: Multiple arguments are not allowed when passing a directory", 377 }, 378 { 379 Name: "multiple files in different directories", 380 Command: fmt.Sprintf("./tflint test.tf %s", filepath.Join("example", "test.tf")), 381 Dir: "example", 382 Status: ExitCodeError, 383 Stderr: fmt.Sprintf("Failed to load `%s`: Multiple files in different directories are not allowed", filepath.Join("example", "test.tf")), 384 }, 385 } 386 387 currentDir, err := os.Getwd() 388 if err != nil { 389 t.Fatal(err) 390 } 391 err = os.Chdir(filepath.Join(currentDir, "test-fixtures", "arguments")) 392 if err != nil { 393 t.Fatal(err) 394 } 395 396 ctrl := gomock.NewController(t) 397 originalRules := rules.DefaultRules 398 399 defer func() { 400 os.Chdir(currentDir) 401 rules.DefaultRules = originalRules 402 ctrl.Finish() 403 }() 404 405 for _, tc := range cases { 406 // Mock rules 407 rules.DefaultRules = []rules.Rule{&testRule{dir: tc.Dir}} 408 409 outStream, errStream := new(bytes.Buffer), new(bytes.Buffer) 410 cli := &CLI{ 411 outStream: outStream, 412 errStream: errStream, 413 testMode: true, 414 } 415 416 loader := tflint.NewMockAbstractLoader(ctrl) 417 loader.EXPECT().LoadConfig(tc.Dir).Return(configs.NewEmptyConfig(), nil).AnyTimes() 418 loader.EXPECT().Files().Return(map[string]*hcl.File{}, nil).AnyTimes() 419 loader.EXPECT().LoadAnnotations(tc.Dir).Return(map[string]tflint.Annotations{}, nil).AnyTimes() 420 loader.EXPECT().LoadValuesFiles().Return([]terraform.InputValues{}, nil).AnyTimes() 421 loader.EXPECT().Sources().Return(map[string][]byte{}).AnyTimes() 422 cli.loader = loader 423 424 status := cli.Run(strings.Split(tc.Command, " ")) 425 426 if status != tc.Status { 427 t.Fatalf("Failed `%s`: Expected status is `%d`, but get `%d`", tc.Name, tc.Status, status) 428 } 429 if !strings.Contains(outStream.String(), tc.Stdout) { 430 t.Fatalf("Failed `%s`: Expected to contain `%s` in stdout, but get `%s`", tc.Name, tc.Stdout, outStream.String()) 431 } 432 if tc.Stdout == "" && outStream.String() != "" { 433 t.Fatalf("Failed `%s`: Expected empty in stdout, but get `%s`", tc.Name, outStream.String()) 434 } 435 if !strings.Contains(errStream.String(), tc.Stderr) { 436 t.Fatalf("Failed `%s`: Expected to contain `%s` in stderr, but get `%s`", tc.Name, tc.Stderr, errStream.String()) 437 } 438 if tc.Stderr == "" && errStream.String() != "" { 439 t.Fatalf("Failed `%s`: Expected empty in stderr, but get `%s`", tc.Name, errStream.String()) 440 } 441 } 442 }