| package tfdiags |
| |
| import ( |
| "errors" |
| "fmt" |
| "reflect" |
| "strings" |
| "testing" |
| |
| "github.com/hashicorp/go-multierror" |
| |
| "github.com/davecgh/go-spew/spew" |
| "github.com/hashicorp/hcl/v2" |
| ) |
| |
| func TestBuild(t *testing.T) { |
| type diagFlat struct { |
| Severity Severity |
| Summary string |
| Detail string |
| Subject *SourceRange |
| Context *SourceRange |
| } |
| |
| tests := map[string]struct { |
| Cons func(Diagnostics) Diagnostics |
| Want []diagFlat |
| }{ |
| "nil": { |
| func(diags Diagnostics) Diagnostics { |
| return diags |
| }, |
| nil, |
| }, |
| "fmt.Errorf": { |
| func(diags Diagnostics) Diagnostics { |
| diags = diags.Append(fmt.Errorf("oh no bad")) |
| return diags |
| }, |
| []diagFlat{ |
| { |
| Severity: Error, |
| Summary: "oh no bad", |
| }, |
| }, |
| }, |
| "errors.New": { |
| func(diags Diagnostics) Diagnostics { |
| diags = diags.Append(errors.New("oh no bad")) |
| return diags |
| }, |
| []diagFlat{ |
| { |
| Severity: Error, |
| Summary: "oh no bad", |
| }, |
| }, |
| }, |
| "hcl.Diagnostic": { |
| func(diags Diagnostics) Diagnostics { |
| diags = diags.Append(&hcl.Diagnostic{ |
| Severity: hcl.DiagError, |
| Summary: "Something bad happened", |
| Detail: "It was really, really bad.", |
| Subject: &hcl.Range{ |
| Filename: "foo.tf", |
| Start: hcl.Pos{Line: 1, Column: 10, Byte: 9}, |
| End: hcl.Pos{Line: 2, Column: 3, Byte: 25}, |
| }, |
| Context: &hcl.Range{ |
| Filename: "foo.tf", |
| Start: hcl.Pos{Line: 1, Column: 1, Byte: 0}, |
| End: hcl.Pos{Line: 3, Column: 1, Byte: 30}, |
| }, |
| }) |
| return diags |
| }, |
| []diagFlat{ |
| { |
| Severity: Error, |
| Summary: "Something bad happened", |
| Detail: "It was really, really bad.", |
| Subject: &SourceRange{ |
| Filename: "foo.tf", |
| Start: SourcePos{Line: 1, Column: 10, Byte: 9}, |
| End: SourcePos{Line: 2, Column: 3, Byte: 25}, |
| }, |
| Context: &SourceRange{ |
| Filename: "foo.tf", |
| Start: SourcePos{Line: 1, Column: 1, Byte: 0}, |
| End: SourcePos{Line: 3, Column: 1, Byte: 30}, |
| }, |
| }, |
| }, |
| }, |
| "hcl.Diagnostics": { |
| func(diags Diagnostics) Diagnostics { |
| diags = diags.Append(hcl.Diagnostics{ |
| { |
| Severity: hcl.DiagError, |
| Summary: "Something bad happened", |
| Detail: "It was really, really bad.", |
| }, |
| { |
| Severity: hcl.DiagWarning, |
| Summary: "Also, somebody sneezed", |
| Detail: "How rude!", |
| }, |
| }) |
| return diags |
| }, |
| []diagFlat{ |
| { |
| Severity: Error, |
| Summary: "Something bad happened", |
| Detail: "It was really, really bad.", |
| }, |
| { |
| Severity: Warning, |
| Summary: "Also, somebody sneezed", |
| Detail: "How rude!", |
| }, |
| }, |
| }, |
| "multierror.Error": { |
| func(diags Diagnostics) Diagnostics { |
| err := multierror.Append(nil, errors.New("bad thing A")) |
| err = multierror.Append(err, errors.New("bad thing B")) |
| diags = diags.Append(err) |
| return diags |
| }, |
| []diagFlat{ |
| { |
| Severity: Error, |
| Summary: "bad thing A", |
| }, |
| { |
| Severity: Error, |
| Summary: "bad thing B", |
| }, |
| }, |
| }, |
| "concat Diagnostics": { |
| func(diags Diagnostics) Diagnostics { |
| var moreDiags Diagnostics |
| moreDiags = moreDiags.Append(errors.New("bad thing A")) |
| moreDiags = moreDiags.Append(errors.New("bad thing B")) |
| return diags.Append(moreDiags) |
| }, |
| []diagFlat{ |
| { |
| Severity: Error, |
| Summary: "bad thing A", |
| }, |
| { |
| Severity: Error, |
| Summary: "bad thing B", |
| }, |
| }, |
| }, |
| "single Diagnostic": { |
| func(diags Diagnostics) Diagnostics { |
| return diags.Append(SimpleWarning("Don't forget your toothbrush!")) |
| }, |
| []diagFlat{ |
| { |
| Severity: Warning, |
| Summary: "Don't forget your toothbrush!", |
| }, |
| }, |
| }, |
| "multiple appends": { |
| func(diags Diagnostics) Diagnostics { |
| diags = diags.Append(SimpleWarning("Don't forget your toothbrush!")) |
| diags = diags.Append(fmt.Errorf("exploded")) |
| return diags |
| }, |
| []diagFlat{ |
| { |
| Severity: Warning, |
| Summary: "Don't forget your toothbrush!", |
| }, |
| { |
| Severity: Error, |
| Summary: "exploded", |
| }, |
| }, |
| }, |
| } |
| |
| for name, test := range tests { |
| t.Run(name, func(t *testing.T) { |
| gotDiags := test.Cons(nil) |
| var got []diagFlat |
| for _, item := range gotDiags { |
| desc := item.Description() |
| source := item.Source() |
| got = append(got, diagFlat{ |
| Severity: item.Severity(), |
| Summary: desc.Summary, |
| Detail: desc.Detail, |
| Subject: source.Subject, |
| Context: source.Context, |
| }) |
| } |
| |
| if !reflect.DeepEqual(got, test.Want) { |
| t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(test.Want)) |
| } |
| }) |
| } |
| } |
| |
| func TestDiagnosticsErr(t *testing.T) { |
| t.Run("empty", func(t *testing.T) { |
| var diags Diagnostics |
| err := diags.Err() |
| if err != nil { |
| t.Errorf("got non-nil error %#v; want nil", err) |
| } |
| }) |
| t.Run("warning only", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(SimpleWarning("bad")) |
| err := diags.Err() |
| if err != nil { |
| t.Errorf("got non-nil error %#v; want nil", err) |
| } |
| }) |
| t.Run("one error", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(errors.New("didn't work")) |
| err := diags.Err() |
| if err == nil { |
| t.Fatalf("got nil error %#v; want non-nil", err) |
| } |
| if got, want := err.Error(), "didn't work"; got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| }) |
| t.Run("two errors", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(errors.New("didn't work")) |
| diags = diags.Append(errors.New("didn't work either")) |
| err := diags.Err() |
| if err == nil { |
| t.Fatalf("got nil error %#v; want non-nil", err) |
| } |
| want := strings.TrimSpace(` |
| 2 problems: |
| |
| - didn't work |
| - didn't work either |
| `) |
| if got := err.Error(); got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| }) |
| t.Run("error and warning", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(errors.New("didn't work")) |
| diags = diags.Append(SimpleWarning("didn't work either")) |
| err := diags.Err() |
| if err == nil { |
| t.Fatalf("got nil error %#v; want non-nil", err) |
| } |
| // Since this "as error" mode is just a fallback for |
| // non-diagnostics-aware situations like tests, we don't actually |
| // distinguish warnings and errors here since the point is to just |
| // get the messages rendered. User-facing code should be printing |
| // each diagnostic separately, so won't enter this codepath, |
| want := strings.TrimSpace(` |
| 2 problems: |
| |
| - didn't work |
| - didn't work either |
| `) |
| if got := err.Error(); got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| }) |
| } |
| |
| func TestDiagnosticsErrWithWarnings(t *testing.T) { |
| t.Run("empty", func(t *testing.T) { |
| var diags Diagnostics |
| err := diags.ErrWithWarnings() |
| if err != nil { |
| t.Errorf("got non-nil error %#v; want nil", err) |
| } |
| }) |
| t.Run("warning only", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(SimpleWarning("bad")) |
| err := diags.ErrWithWarnings() |
| if err == nil { |
| t.Errorf("got nil error; want NonFatalError") |
| return |
| } |
| if _, ok := err.(NonFatalError); !ok { |
| t.Errorf("got %T; want NonFatalError", err) |
| } |
| }) |
| t.Run("one error", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(errors.New("didn't work")) |
| err := diags.ErrWithWarnings() |
| if err == nil { |
| t.Fatalf("got nil error %#v; want non-nil", err) |
| } |
| if got, want := err.Error(), "didn't work"; got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| }) |
| t.Run("two errors", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(errors.New("didn't work")) |
| diags = diags.Append(errors.New("didn't work either")) |
| err := diags.ErrWithWarnings() |
| if err == nil { |
| t.Fatalf("got nil error %#v; want non-nil", err) |
| } |
| want := strings.TrimSpace(` |
| 2 problems: |
| |
| - didn't work |
| - didn't work either |
| `) |
| if got := err.Error(); got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| }) |
| t.Run("error and warning", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(errors.New("didn't work")) |
| diags = diags.Append(SimpleWarning("didn't work either")) |
| err := diags.ErrWithWarnings() |
| if err == nil { |
| t.Fatalf("got nil error %#v; want non-nil", err) |
| } |
| // Since this "as error" mode is just a fallback for |
| // non-diagnostics-aware situations like tests, we don't actually |
| // distinguish warnings and errors here since the point is to just |
| // get the messages rendered. User-facing code should be printing |
| // each diagnostic separately, so won't enter this codepath, |
| want := strings.TrimSpace(` |
| 2 problems: |
| |
| - didn't work |
| - didn't work either |
| `) |
| if got := err.Error(); got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| }) |
| } |
| |
| func TestDiagnosticsNonFatalErr(t *testing.T) { |
| t.Run("empty", func(t *testing.T) { |
| var diags Diagnostics |
| err := diags.NonFatalErr() |
| if err != nil { |
| t.Errorf("got non-nil error %#v; want nil", err) |
| } |
| }) |
| t.Run("warning only", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(SimpleWarning("bad")) |
| err := diags.NonFatalErr() |
| if err == nil { |
| t.Errorf("got nil error; want NonFatalError") |
| return |
| } |
| if _, ok := err.(NonFatalError); !ok { |
| t.Errorf("got %T; want NonFatalError", err) |
| } |
| }) |
| t.Run("one error", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(errors.New("didn't work")) |
| err := diags.NonFatalErr() |
| if err == nil { |
| t.Fatalf("got nil error %#v; want non-nil", err) |
| } |
| if got, want := err.Error(), "didn't work"; got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| if _, ok := err.(NonFatalError); !ok { |
| t.Errorf("got %T; want NonFatalError", err) |
| } |
| }) |
| t.Run("two errors", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(errors.New("didn't work")) |
| diags = diags.Append(errors.New("didn't work either")) |
| err := diags.NonFatalErr() |
| if err == nil { |
| t.Fatalf("got nil error %#v; want non-nil", err) |
| } |
| want := strings.TrimSpace(` |
| 2 problems: |
| |
| - didn't work |
| - didn't work either |
| `) |
| if got := err.Error(); got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| if _, ok := err.(NonFatalError); !ok { |
| t.Errorf("got %T; want NonFatalError", err) |
| } |
| }) |
| t.Run("error and warning", func(t *testing.T) { |
| var diags Diagnostics |
| diags = diags.Append(errors.New("didn't work")) |
| diags = diags.Append(SimpleWarning("didn't work either")) |
| err := diags.NonFatalErr() |
| if err == nil { |
| t.Fatalf("got nil error %#v; want non-nil", err) |
| } |
| // Since this "as error" mode is just a fallback for |
| // non-diagnostics-aware situations like tests, we don't actually |
| // distinguish warnings and errors here since the point is to just |
| // get the messages rendered. User-facing code should be printing |
| // each diagnostic separately, so won't enter this codepath, |
| want := strings.TrimSpace(` |
| 2 problems: |
| |
| - didn't work |
| - didn't work either |
| `) |
| if got := err.Error(); got != want { |
| t.Errorf("wrong error message\ngot: %s\nwant: %s", got, want) |
| } |
| if _, ok := err.(NonFatalError); !ok { |
| t.Errorf("got %T; want NonFatalError", err) |
| } |
| }) |
| } |