| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "context" |
| "encoding/json" |
| "os" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/go-cmp/cmp/cmpopts" |
| |
| "go.fuchsia.dev/fuchsia/tools/build" |
| "go.fuchsia.dev/fuchsia/tools/staticanalysis" |
| ) |
| |
| func TestClippyReporter(t *testing.T) { |
| tests := []struct { |
| name string |
| path string |
| clippyTargets []build.ClippyTarget |
| clippyOutputs map[string][]clippyResult |
| expected []*staticanalysis.Finding |
| }{ |
| { |
| name: "file with no clippy target", |
| path: "src/bar.rs", |
| clippyTargets: []build.ClippyTarget{ |
| { |
| Output: "bar.clippy", |
| Sources: []string{"../../src/foo.rs"}, |
| }, |
| }, |
| expected: nil, |
| }, |
| { |
| name: "file with clippy target", |
| path: "src/foo.rs", |
| clippyTargets: []build.ClippyTarget{ |
| { |
| Output: "foo.clippy", |
| Sources: []string{"../../src/foo.rs"}, |
| }, |
| }, |
| clippyOutputs: map[string][]clippyResult{ |
| "foo.clippy": { |
| // Results without any primary span should be ignored. |
| { |
| Message: "1 lint failed", |
| Code: clippyCode{Code: "clippy::summary"}, |
| Level: "warning", |
| }, |
| { |
| Message: "casting integer literal to `u64` is unnecessary", |
| Code: clippyCode{Code: "clippy::unnecessary_cast"}, |
| Level: "warning", |
| Spans: []clippySpan{ |
| { |
| FileName: "../../src/foo.rs", |
| // Non-primary spans should be ignored. |
| Primary: false, |
| LineStart: 1, |
| LineEnd: 1, |
| ColumnStart: 4, |
| ColumnEnd: 5, |
| }, |
| { |
| FileName: "../../src/foo.rs", |
| Primary: true, |
| LineStart: 100, |
| LineEnd: 100, |
| ColumnStart: 14, |
| ColumnEnd: 24, |
| }, |
| }, |
| Children: []clippyResult{ |
| { |
| Message: "I am a note", |
| // Children with level != "help" should be ignored. |
| Level: "note", |
| }, |
| { |
| Message: "try", |
| Level: "help", |
| Spans: []clippySpan{ |
| { |
| Primary: true, |
| SuggestedReplacement: "123_u64", |
| FileName: "../../src/foo.rs", |
| LineStart: 100, |
| LineEnd: 100, |
| ColumnStart: 14, |
| ColumnEnd: 24, |
| }, |
| }, |
| }, |
| { |
| // This isn't realistic, but it covers the logic |
| // to ignore multiline suggested replacements. |
| Message: "or try", |
| Level: "help", |
| Spans: []clippySpan{ |
| { |
| Primary: true, |
| SuggestedReplacement: "multiline\nreplacement", |
| FileName: "../../src/foo.rs", |
| LineStart: 50, |
| LineEnd: 51, |
| ColumnStart: 7, |
| ColumnEnd: 2, |
| }, |
| { |
| Primary: true, |
| SuggestedReplacement: "second_part_of_replacement", |
| FileName: "../../src/foo.rs", |
| LineStart: 60, |
| LineEnd: 61, |
| ColumnStart: 5, |
| ColumnEnd: 6, |
| }, |
| }, |
| }, |
| { |
| Message: "see https://clippy.example.com for docs", |
| Level: "help", |
| }, |
| }, |
| }, |
| }, |
| }, |
| expected: []*staticanalysis.Finding{ |
| { |
| Message: strings.Join([]string{ |
| "casting integer literal to `u64` is unnecessary", |
| "help: try: `123_u64`", |
| "help: see https://clippy.example.com for docs", |
| "To reproduce locally, run `fx clippy -f src/foo.rs`", |
| }, "\n\n"), |
| Category: "Clippy/warning/unnecessary_cast", |
| Path: "src/foo.rs", |
| Line: 100, |
| EndLine: 100, |
| Col: 14, |
| EndCol: 24, |
| Replacements: []string{ |
| "123_u64", |
| }, |
| }, |
| }, |
| }, |
| { |
| name: "file with empty clippy output", |
| path: "src/foo.rs", |
| clippyTargets: []build.ClippyTarget{ |
| { |
| Output: "foo.clippy", |
| Sources: []string{"../../src/foo.rs"}, |
| }, |
| }, |
| clippyOutputs: map[string][]clippyResult{ |
| "foo.clippy": nil, |
| }, |
| expected: nil, |
| }, |
| } |
| |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| checkoutDir := t.TempDir() |
| buildDir := filepath.Join(checkoutDir, "out", "testing") |
| if err := os.MkdirAll(buildDir, 0o700); err != nil { |
| t.Fatal(err) |
| } |
| |
| analyzer := clippyReporter{ |
| checkoutDir: checkoutDir, |
| buildDir: buildDir, |
| clippyTargets: test.clippyTargets, |
| } |
| |
| for path, results := range test.clippyOutputs { |
| f, err := os.Create(filepath.Join(buildDir, path)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| enc := json.NewEncoder(f) |
| for _, result := range results { |
| // Encode() automatically appends a newline, so the file will be in JSON |
| // lines format. |
| if err := enc.Encode(result); err != nil { |
| t.Fatal(err) |
| } |
| } |
| } |
| |
| findings, err := analyzer.report(context.Background(), test.path) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if diff := cmp.Diff(test.expected, findings, cmpopts.EquateEmpty()); diff != "" { |
| t.Errorf("clippy analyzer diff (-want +got):\n%s", diff) |
| } |
| }) |
| } |
| } |