Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions analyzers/otelresource/otelresource.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func run(pass *analysis.Pass) (any, error) { //nolint:gocyclo // wrapper + direc
return false
})

// Pass 2: call sites of those wrappers (one hop) — medium confidence.
// Pass 2: call sites of those wrappers (one hop) - medium confidence.
insp.Nodes([]ast.Node{(*ast.CallExpr)(nil)}, func(n ast.Node, push bool) bool {
if !push {
return false
Expand All @@ -70,7 +70,7 @@ func run(pass *analysis.Pass) (any, error) { //nolint:gocyclo // wrapper + direc
if fn == nil || !wrappers[fn] {
return false
}
// Don't double-report the resource.New site inside the wrapper body.
// Skip the inner resource.New; pass 1 already reported it.
if isResourceNew(pass, call) {
return false
}
Expand Down
52 changes: 51 additions & 1 deletion cli/check_cardinality_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestCheckCardinalityFiltersRules(t *testing.T) {
}
out := buf.String()
if !strings.Contains(out, "metrics/high-cardinality-label") && !strings.Contains(out, "high-cardinality") {
// text format prints message, not always rule id — accept either
// text format prints message, not always rule id - accept either
if !strings.Contains(out, "unbounded") && !strings.Contains(out, "billable") {
t.Fatalf("expected cardinality output, got %q", out)
}
Expand All @@ -40,3 +40,53 @@ func TestIsCardinalityRule(t *testing.T) {
t.Fatal("unexpected match")
}
}

func TestCheckCardinalityCleanTree(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("ok\n"), 0o600); err != nil {
t.Fatal(err)
}
root := NewRootCommand()
buf := &bytes.Buffer{}
root.SetOut(buf)
root.SetArgs([]string{"check-cardinality", dir})
if err := root.ExecuteContext(context.Background()); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "ok: no cardinality findings") {
t.Fatalf("want ok banner, got %q", buf.String())
}
}

func TestCheckCardinalityFailOn(t *testing.T) {
if _, err := os.Stat(filepath.Join("..", "fixtures", "_badshop")); err != nil {
t.Skip("fixtures not available")
}
root := NewRootCommand()
root.SetOut(&bytes.Buffer{})
root.SetErr(&bytes.Buffer{})
root.SetArgs([]string{
"check-cardinality",
"--fail-on", "warning",
filepath.Join("..", "fixtures", "_badshop"),
})
err := root.ExecuteContext(context.Background())
if err == nil {
t.Fatal("want fail-on threshold error")
}
if !strings.Contains(err.Error(), "findings at or above") {
t.Fatalf("got %v", err)
}
}

func TestCheckCardinalityInvalidFailOn(t *testing.T) {
dir := t.TempDir()
root := NewRootCommand()
root.SetOut(&bytes.Buffer{})
root.SetErr(&bytes.Buffer{})
root.SetArgs([]string{"check-cardinality", "--fail-on", "loud", dir})
err := root.ExecuteContext(context.Background())
if err == nil || !strings.Contains(err.Error(), "invalid --fail-on") {
t.Fatalf("want invalid --fail-on, got %v", err)
}
}
2 changes: 1 addition & 1 deletion cli/failon.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const (
failOnError = "error"
)

// failOnError is returned after a successful scan when --fail-on is tripped.
// failOnThresholdError is returned after a successful scan when --fail-on is tripped.
type failOnThresholdError struct {
threshold string
count int
Expand Down
61 changes: 56 additions & 5 deletions cli/failon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,63 @@ import (
func TestFailOnRank(t *testing.T) {
t.Parallel()

if _, err := failOnRank("loud"); err == nil || !strings.Contains(err.Error(), "invalid --fail-on") {
t.Fatalf("typo'd threshold must fail, got %v", err)
tests := []struct {
level string
want int
err bool
}{
{level: "", want: 0},
{level: failOnNone, want: 0},
{level: "hint", want: 1},
{level: failOnInfo, want: 2},
{level: failOnWarning, want: 3},
{level: failOnError, want: 4},
{level: "loud", err: true},
}
n, err := failOnRank(failOnNone)
if err != nil || n != 0 {
t.Fatalf("none = %d, %v; want 0, nil", n, err)
for _, tt := range tests {
t.Run(tt.level, func(t *testing.T) {
t.Parallel()
n, err := failOnRank(tt.level)
if tt.err {
if err == nil || !strings.Contains(err.Error(), "invalid --fail-on") {
t.Fatalf("want invalid --fail-on error, got %v", err)
}
return
}
if err != nil || n != tt.want {
t.Fatalf("failOnRank(%q) = %d, %v; want %d, nil", tt.level, n, err, tt.want)
}
})
}
}

func TestSeverityRank(t *testing.T) {
t.Parallel()

tests := []struct {
sev string
want int
}{
{sev: "hint", want: 1},
{sev: "info", want: 2},
{sev: "warning", want: 3},
{sev: "error", want: 4},
{sev: "unknown", want: 0},
{sev: "", want: 0},
}
for _, tt := range tests {
if got := severityRank(tt.sev); got != tt.want {
t.Errorf("severityRank(%q) = %d, want %d", tt.sev, got, tt.want)
}
}
}

func TestFailOnThresholdErrorMessage(t *testing.T) {
t.Parallel()

err := failOnThresholdError{threshold: "warning", count: 3}
if got := err.Error(); got != "3 findings at or above warning" {
t.Fatalf("Error() = %q", got)
}
}

Expand Down
141 changes: 140 additions & 1 deletion cli/scan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"bytes"
"errors"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -122,19 +123,157 @@ func TestSourcePath(t *testing.T) {
t.Parallel()

dir := t.TempDir()
file := filepath.Join(dir, "sub", "a.go")
sub := filepath.Join(dir, "sub")
if err := os.MkdirAll(sub, 0o750); err != nil {
t.Fatal(err)
}
file := filepath.Join(sub, "a.go")
if err := os.WriteFile(file, []byte("package p\n"), 0o600); err != nil {
t.Fatal(err)
}

if got := sourcePath(dir, filepath.Join("sub", "a.go")); got != file {
t.Errorf("dir root joins: got %q want %q", got, file)
}
if got := sourcePath(file, file); got != file {
t.Errorf("single-file root returns the input: got %q want %q", got, file)
}
if got := sourcePath(file, "a.go"); got != file {
t.Errorf("single-file root + basename: got %q want %q", got, file)
}
if got := sourcePath(file, "other.go"); got != filepath.Join(sub, "other.go") {
t.Errorf("single-file root + sibling: got %q", got)
}
if got := sourcePath("/repo", "/abs/a.go"); got != "/abs/a.go" {
t.Errorf("absolute finding passes through: got %q", got)
}
}

func TestRenderRejectsUnknownFormat(t *testing.T) {
t.Parallel()

var out bytes.Buffer
err := render(&out, nil, "yaml", ".")
if err == nil || !strings.Contains(err.Error(), "invalid --output") {
t.Fatalf("want invalid --output error, got %v", err)
}
}

func TestScanCommand(t *testing.T) {
badshop := filepath.Join("..", "fixtures", "_badshop")
if _, err := os.Stat(badshop); err != nil {
t.Skip("fixtures not available")
}
godeep := filepath.Join("..", "fixtures", "godeep")
clean := t.TempDir()
if err := os.WriteFile(filepath.Join(clean, "README.md"), []byte("ok\n"), 0o600); err != nil {
t.Fatal(err)
}

t.Run("pattern scan on fixture finds issues", func(t *testing.T) {
root := NewRootCommand()
buf := &bytes.Buffer{}
root.SetOut(buf)
root.SetArgs([]string{"scan", "--engine", "pattern", "--output", "json", badshop})
if err := root.Execute(); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), `"rule"`) {
t.Fatalf("expected findings JSON, got %q", buf.String())
}
})

t.Run("go engine on godeep", func(t *testing.T) {
root := NewRootCommand()
buf := &bytes.Buffer{}
root.SetOut(buf)
root.SetArgs([]string{"scan", "--engine", "go", "--output", "text", godeep})
if err := root.Execute(); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "findings") {
t.Fatalf("expected text summary, got %q", buf.String())
}
})

t.Run("clean tree with fail-on stays quiet", func(t *testing.T) {
root := NewRootCommand()
buf := &bytes.Buffer{}
root.SetOut(buf)
root.SetArgs([]string{"scan", "--engine", "pattern", "--fail-on", "warning", clean})
if err := root.Execute(); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "0 findings") {
t.Fatalf("got %q", buf.String())
}
})

t.Run("invalid engine", func(t *testing.T) {
root := NewRootCommand()
root.SetOut(&bytes.Buffer{})
root.SetErr(&bytes.Buffer{})
root.SetArgs([]string{"scan", "--engine", "wasm", clean})
err := root.Execute()
if err == nil || !strings.Contains(err.Error(), "invalid --engine") {
t.Fatalf("want invalid --engine, got %v", err)
}
})

t.Run("invalid fail-on", func(t *testing.T) {
root := NewRootCommand()
root.SetOut(&bytes.Buffer{})
root.SetErr(&bytes.Buffer{})
root.SetArgs([]string{"scan", "--fail-on", "loud", clean})
err := root.Execute()
if err == nil || !strings.Contains(err.Error(), "invalid --fail-on") {
t.Fatalf("want invalid --fail-on, got %v", err)
}
})

t.Run("fail-on trips on findings", func(t *testing.T) {
root := NewRootCommand()
root.SetOut(&bytes.Buffer{})
root.SetErr(&bytes.Buffer{})
root.SetArgs([]string{"scan", "--engine", "pattern", "--fail-on", "warning", badshop})
err := root.Execute()
if err == nil {
t.Fatal("want fail-on threshold error")
}
var thresh failOnThresholdError
if !errors.As(err, &thresh) && !strings.Contains(err.Error(), "findings at or above") {
t.Fatalf("want threshold error, got %v", err)
}
})

t.Run("explicit rules config", func(t *testing.T) {
cfg, err := filepath.Abs(filepath.Join("..", "sgconfig.yml"))
if err != nil {
t.Fatal(err)
}
root := NewRootCommand()
buf := &bytes.Buffer{}
root.SetOut(buf)
root.SetArgs([]string{"scan", "--rules", cfg, "--engine", "pattern", clean})
if err := root.Execute(); err != nil {
t.Fatal(err)
}
})

t.Run("html output", func(t *testing.T) {
root := NewRootCommand()
buf := &bytes.Buffer{}
root.SetOut(buf)
root.SetArgs([]string{"scan", "--engine", "pattern", "--output", "html", clean})
if err := root.Execute(); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "<!DOCTYPE html>") {
t.Fatalf("want html, got %q", buf.String())
}
})
}

func writeSource(t *testing.T, dir, body string) {
t.Helper()
if err := os.WriteFile(filepath.Join(dir, "a.go"), []byte(body), 0o600); err != nil {
Expand Down
23 changes: 15 additions & 8 deletions cli/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"encoding/json"
"fmt"
"io"
"runtime/debug"

"github.com/spf13/cobra"
Expand All @@ -29,21 +30,27 @@ func newVersionCommand() *cobra.Command {
enc.SetIndent("", " ")
return enc.Encode(info)
}
dirty := ""
if info.Dirty {
dirty = "-dirty"
}
_, err := fmt.Fprintf(cmd.OutOrStdout(), "%s %s%s (%s)\n",
info.Module, info.Revision, dirty, info.Go)
_, err := io.WriteString(cmd.OutOrStdout(), formatVersionText(info))
return err
},
}
}

func formatVersionText(info buildInfo) string {
dirty := ""
if info.Dirty {
dirty = "-dirty"
}
return fmt.Sprintf("%s %s%s (%s)\n", info.Module, info.Revision, dirty, info.Go)
}

func readBuildInfo() buildInfo {
return buildInfoFrom(debug.ReadBuildInfo())
}

func buildInfoFrom(raw *debug.BuildInfo, ok bool) buildInfo {
info := buildInfo{Module: "tarsier", Revision: "unknown", Go: "unknown"}
raw, ok := debug.ReadBuildInfo()
if !ok {
if !ok || raw == nil {
return info
}
info.Module, info.Go = raw.Main.Path, raw.GoVersion
Expand Down
Loading
Loading