diff --git a/.gitignore b/.gitignore index f60e9f6..c615fac 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ logs bin/* !bin/.gitkeep megalinter-reports +dist/* diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..4e4cab4 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,79 @@ +version: 2 + +before: + hooks: + - go mod tidy + - go generate ./... + +builds: + - main: ./cmd/main.go + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + goarch: + - amd64 + - arm64 + - arm + goarm: + - "7" + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{ .CommitDate }} -X main.builtBy=goreleaser + +archives: + - name_template: >- + {{ .ProjectName }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + builds_info: + group: root + owner: root + files: + - LICENSE* + - README* + - CHANGELOG* + +checksum: + name_template: 'checksums.txt' + +snapshot: + version_template: "{{ incpatch .Version }}-next" + +changelog: + sort: asc + use: github + filters: + exclude: + - '^docs:' + - '^test:' + - '^chore:' + - Merge pull request + - Merge branch + - go mod tidy + groups: + - title: 'New Features' + regexp: "^.*feat[(\\w)]*:+.*$" + order: 0 + - title: 'Bug fixes' + regexp: "^.*fix[(\\w)]*:+.*$" + order: 1 + - title: Other work + order: 999 + +release: + github: + owner: ivuorinen + name: go-test-sarif + draft: true + prerelease: auto + mode: append + name_template: "{{.ProjectName}}-v{{.Version}}" + disable: false diff --git a/Justfile b/Justfile index 53f0a05..59e8d01 100644 --- a/Justfile +++ b/Justfile @@ -44,3 +44,27 @@ docker-build: docker-run: echo "Running {{app_name}} in Docker..." docker run --rm -v $(pwd):/workspace ghcr.io/ivuorinen/{{app_name}} go-test-results.json go-test-results.sarif + +# Check if goreleaser is installed +check-goreleaser: + @which goreleaser > /dev/null || (echo "goreleaser not found. Please install from https://goreleaser.com/install/" && exit 1) + +# Create a snapshot release (for testing) +release-snapshot: check-goreleaser + echo "Creating snapshot release..." + goreleaser release --snapshot --clean + +# Create a local release (without publishing) +release-local: check-goreleaser + echo "Creating local release..." + goreleaser release --skip=publish --clean + +# Create and publish a release (requires GITHUB_TOKEN) +release: check-goreleaser + echo "Creating and publishing release..." + GITHUB_TOKEN=$(gh auth token) goreleaser release --clean + +# Validate goreleaser configuration +release-check: check-goreleaser + echo "Checking goreleaser configuration..." + goreleaser check diff --git a/.github/README.md b/README.md similarity index 100% rename from .github/README.md rename to README.md diff --git a/cmd/main.go b/cmd/main.go index 3d968fe..77bb1c1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,25 +1,65 @@ -// main package contains the cli functionality package main import ( + "flag" "fmt" + "io" "os" "github.com/ivuorinen/go-test-sarif-action/internal" ) -func main() { - if len(os.Args) < 3 { - fmt.Println("Usage: go-test-sarif ") - os.Exit(1) - } +var ( + version = "dev" + commit = "none" + date = "unknown" + builtBy = "unknown" +) - inputFile := os.Args[1] - outputFile := os.Args[2] - - err := internal.ConvertToSARIF(inputFile, outputFile) - if err != nil { - fmt.Println("Error:", err) - os.Exit(1) - } +func printVersion(w io.Writer) { + _, _ = fmt.Fprintf(w, "go-test-sarif %s\n", version) + _, _ = fmt.Fprintf(w, " commit: %s\n", commit) + _, _ = fmt.Fprintf(w, " built at: %s\n", date) + _, _ = fmt.Fprintf(w, " built by: %s\n", builtBy) +} + +func printUsage(w io.Writer) { + _, _ = fmt.Fprintln(w, "Usage: go-test-sarif ") + _, _ = fmt.Fprintln(w, " go-test-sarif --version") +} + +func run(args []string, stdout, stderr io.Writer) int { + fs := flag.NewFlagSet("go-test-sarif", flag.ContinueOnError) + fs.SetOutput(stderr) + + var versionFlag bool + fs.BoolVar(&versionFlag, "version", false, "Display version information") + fs.BoolVar(&versionFlag, "v", false, "Display version information (short)") + if err := fs.Parse(args[1:]); err != nil { + return 1 + } + + if versionFlag { + printVersion(stdout) + return 0 + } + + if fs.NArg() < 2 { + printUsage(stderr) + return 1 + } + + inputFile := fs.Arg(0) + outputFile := fs.Arg(1) + + if err := internal.ConvertToSARIF(inputFile, outputFile); err != nil { + _, _ = fmt.Fprintf(stderr, "Error: %v\n", err) + return 1 + } + + return 0 +} + +func main() { + os.Exit(run(os.Args, os.Stdout, os.Stderr)) } diff --git a/cmd/main_test.go b/cmd/main_test.go new file mode 100644 index 0000000..df0e80a --- /dev/null +++ b/cmd/main_test.go @@ -0,0 +1,147 @@ +package main + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestRun(t *testing.T) { + tests := []struct { + name string + args []string + setupFunc func() (string, string, func()) + wantExit int + wantStdout string + wantStderr string + }{ + { + name: "version flag long", + args: []string{"go-test-sarif", "--version"}, + wantExit: 0, + wantStdout: "go-test-sarif dev", + }, + { + name: "version flag short", + args: []string{"go-test-sarif", "-v"}, + wantExit: 0, + wantStdout: "go-test-sarif dev", + }, + { + name: "missing arguments", + args: []string{"go-test-sarif"}, + wantExit: 1, + wantStderr: "Usage: go-test-sarif", + }, + { + name: "only one argument", + args: []string{"go-test-sarif", "input.json"}, + wantExit: 1, + wantStderr: "Usage: go-test-sarif", + }, + { + name: "valid conversion", + args: []string{"go-test-sarif", "input.json", "output.sarif"}, + setupFunc: setupValidTestFiles, + wantExit: 0, + }, + { + name: "invalid input file", + args: []string{"go-test-sarif", "nonexistent.json", "output.sarif"}, + wantExit: 1, + wantStderr: "Error:", + }, + { + name: "invalid flag", + args: []string{"go-test-sarif", "--invalid"}, + wantExit: 1, + wantStderr: "flag provided but not defined", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var cleanup func() + if tt.setupFunc != nil { + inputFile, outputFile, cleanupFunc := tt.setupFunc() + cleanup = cleanupFunc + // Replace placeholders with actual file paths + for i, arg := range tt.args { + switch arg { + case "input.json": + tt.args[i] = inputFile + case "output.sarif": + tt.args[i] = outputFile + } + } + } + if cleanup != nil { + defer cleanup() + } + + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + + exitCode := run(tt.args, stdout, stderr) + + if exitCode != tt.wantExit { + t.Errorf("run() exit code = %v, want %v", exitCode, tt.wantExit) + } + + if tt.wantStdout != "" && !strings.Contains(stdout.String(), tt.wantStdout) { + t.Errorf("stdout = %q, want to contain %q", stdout.String(), tt.wantStdout) + } + + if tt.wantStderr != "" && !strings.Contains(stderr.String(), tt.wantStderr) { + t.Errorf("stderr = %q, want to contain %q", stderr.String(), tt.wantStderr) + } + }) + } +} + +func TestPrintVersion(t *testing.T) { + buf := &bytes.Buffer{} + printVersion(buf) + + output := buf.String() + if !strings.Contains(output, "go-test-sarif dev") { + t.Errorf("printVersion() = %q, want to contain %q", output, "go-test-sarif dev") + } + if !strings.Contains(output, "commit: none") { + t.Errorf("printVersion() = %q, want to contain %q", output, "commit: none") + } +} + +func TestPrintUsage(t *testing.T) { + buf := &bytes.Buffer{} + printUsage(buf) + + output := buf.String() + if !strings.Contains(output, "Usage: go-test-sarif ") { + t.Errorf("printUsage() = %q, want to contain usage information", output) + } +} + +func setupValidTestFiles() (string, string, func()) { + tmpDir, err := os.MkdirTemp("", "go-test-sarif-test") + if err != nil { + panic(err) + } + + inputFile := filepath.Join(tmpDir, "test-input.json") + outputFile := filepath.Join(tmpDir, "test-output.sarif") + + // Create a valid test JSON file + testJSON := `{"Time":"2023-01-01T00:00:00Z","Action":"pass","Package":"example.com/test","Test":"TestExample","Elapsed":0.1}` + if err := os.WriteFile(inputFile, []byte(testJSON), 0644); err != nil { + panic(err) + } + + cleanup := func() { + _ = os.RemoveAll(tmpDir) + } + + return inputFile, outputFile, cleanup +} diff --git a/internal/converter.go b/internal/converter.go index 668f136..b7e30c5 100644 --- a/internal/converter.go +++ b/internal/converter.go @@ -22,17 +22,13 @@ type TestEvent struct { func ConvertToSARIF(inputFile, outputFile string) error { f, err := os.Open(inputFile) if err != nil { - return fmt.Errorf("failed to read input file: %w", err) + return err } - defer func() { - if cerr := f.Close(); cerr != nil { - fmt.Fprintf(os.Stderr, "failed to close input file: %v\n", cerr) - } - }() + defer func() { _ = f.Close() }() report, err := sarif.New(sarif.Version210) if err != nil { - return fmt.Errorf("failed to create SARIF report: %w", err) + return err } run := sarif.NewRunWithInformationURI("go-test-sarif", "https://golang.org/cmd/go/#hdr-Test_packages") @@ -42,22 +38,23 @@ func ConvertToSARIF(inputFile, outputFile string) error { for scanner.Scan() { var event TestEvent if err := json.Unmarshal(scanner.Bytes(), &event); err != nil { - return fmt.Errorf("invalid JSON format: %w", err) + return fmt.Errorf("invalid JSON: %w", err) } if event.Action == "fail" && (event.Test != "" || event.Package != "") { - res := sarif.NewRuleResult(rule.ID). + result := sarif.NewRuleResult(rule.ID). WithLevel("error"). WithMessage(sarif.NewTextMessage(event.Output)) - run.AddResult(res) + run.AddResult(result) } } + if err := scanner.Err(); err != nil { - return fmt.Errorf("failed to scan input file: %w", err) + return err } report.AddRun(run) if err := report.WriteFile(outputFile); err != nil { - return fmt.Errorf("failed to write SARIF output file: %w", err) + return err } fmt.Printf("SARIF report generated: %s\n", outputFile)