Merge pull request #21214 from github/mbg/go/diagnostics-unit-tests

Go: Make diagnostics unit-testable and add test for `EmitCannotFindPackages`
This commit is contained in:
Michael B. Gale
2026-01-26 10:44:03 +00:00
committed by GitHub
6 changed files with 177 additions and 40 deletions

View File

@@ -1,6 +1,6 @@
# generated running `bazel run //go/gazelle`, do not edit
load("@rules_go//go:def.bzl", "go_library")
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "diagnostics",
@@ -9,3 +9,10 @@ go_library(
visibility = ["//visibility:public"],
deps = ["//go/extractor/util"],
)
go_test(
name = "diagnostics_test",
srcs = ["diagnostics_test.go"],
embed = [":diagnostics"],
deps = ["@com_github_stretchr_testify//assert"],
)

View File

@@ -3,7 +3,7 @@ package diagnostics
import (
"encoding/json"
"fmt"
"log"
"log/slog"
"os"
"strings"
"time"
@@ -56,19 +56,66 @@ type diagnostic struct {
var diagnosticsEmitted, diagnosticsLimit uint = 0, 100
var noDiagnosticDirPrinted bool = false
func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) {
type DiagnosticsWriter interface {
WriteDiagnostic(d diagnostic)
}
type FileDiagnosticsWriter struct {
diagnosticDir string
}
func (writer *FileDiagnosticsWriter) WriteDiagnostic(d diagnostic) {
if writer == nil {
return
}
content, err := json.Marshal(d)
if err != nil {
slog.Error("Failed to encode diagnostic as JSON", slog.Any("err", err))
return
}
targetFile, err := os.CreateTemp(writer.diagnosticDir, "go-extractor.*.json")
if err != nil {
slog.Error("Failed to create diagnostic file", slog.Any("err", err))
return
}
defer func() {
if err := targetFile.Close(); err != nil {
slog.Error("Failed to close diagnostic file", slog.Any("err", err))
}
}()
_, err = targetFile.Write(content)
if err != nil {
slog.Error("Failed to write to diagnostic file", slog.Any("err", err))
}
}
var DefaultWriter *FileDiagnosticsWriter = nil
func NewFileDiagnosticsWriter() *FileDiagnosticsWriter {
diagnosticDir := os.Getenv("CODEQL_EXTRACTOR_GO_DIAGNOSTIC_DIR")
if diagnosticDir == "" {
if !noDiagnosticDirPrinted {
slog.Warn("No diagnostic directory set, so not emitting diagnostics")
noDiagnosticDirPrinted = true
}
return nil
}
return &FileDiagnosticsWriter{diagnosticDir}
}
func init() {
DefaultWriter = NewFileDiagnosticsWriter()
}
// Emits a diagnostic using the specified `DiagnosticsWriter`.
func emitDiagnosticTo(writer DiagnosticsWriter, sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) {
if diagnosticsEmitted < diagnosticsLimit {
diagnosticsEmitted += 1
diagnosticDir := os.Getenv("CODEQL_EXTRACTOR_GO_DIAGNOSTIC_DIR")
if diagnosticDir == "" {
if !noDiagnosticDirPrinted {
log.Println("No diagnostic directory set, so not emitting diagnostic")
noDiagnosticDirPrinted = true
}
return
}
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.000") + "Z"
var d diagnostic
@@ -93,33 +140,15 @@ func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagn
}
}
content, err := json.Marshal(d)
if err != nil {
log.Println(err)
return
}
targetFile, err := os.CreateTemp(diagnosticDir, "go-extractor.*.json")
if err != nil {
log.Println("Failed to create diagnostic file: ")
log.Println(err)
return
}
defer func() {
if err := targetFile.Close(); err != nil {
log.Println("Failed to close diagnostic file:")
log.Println(err)
}
}()
_, err = targetFile.Write(content)
if err != nil {
log.Println("Failed to write to diagnostic file: ")
log.Println(err)
}
writer.WriteDiagnostic(d)
}
}
// Emits a diagnostic using the default `DiagnosticsWriter`.
func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) {
emitDiagnosticTo(DefaultWriter, sourceid, sourcename, markdownMessage, severity, visibility, location)
}
func EmitPackageDifferentOSArchitecture(pkgPath string) {
emitDiagnostic(
"go/autobuilder/package-different-os-architecture",
@@ -141,7 +170,7 @@ func plural(n int, singular, plural string) string {
const maxNumPkgPaths = 5
func EmitCannotFindPackages(pkgPaths []string) {
func EmitCannotFindPackages(writer DiagnosticsWriter, pkgPaths []string) {
numPkgPaths := len(pkgPaths)
numPrinted := numPkgPaths
@@ -188,7 +217,8 @@ func EmitCannotFindPackages(pkgPaths []string) {
"If any of the packages are already present in the repository, but were not found, then you may need a [custom build command](https://docs.github.com/en/code-security/how-tos/scan-code-for-vulnerabilities/manage-your-configuration/codeql-code-scanning-for-compiled-languages)."
}
emitDiagnostic(
emitDiagnosticTo(
writer,
"go/autobuilder/package-not-found",
"Some packages could not be found",
message,

View File

@@ -0,0 +1,85 @@
package diagnostics
import (
"testing"
"github.com/stretchr/testify/assert"
)
type memoryDiagnosticsWriter struct {
diagnostics []diagnostic
}
func newMemoryDiagnosticsWriter() *memoryDiagnosticsWriter {
return &memoryDiagnosticsWriter{[]diagnostic{}}
}
func (writer *memoryDiagnosticsWriter) WriteDiagnostic(d diagnostic) {
writer.diagnostics = append(writer.diagnostics, d)
}
func Test_EmitCannotFindPackages_Default(t *testing.T) {
writer := newMemoryDiagnosticsWriter()
// Clear environment variables that affect the diagnostic message.
t.Setenv("GITHUB_EVENT_NAME", "")
t.Setenv("GITHUB_ACTIONS", "")
EmitCannotFindPackages(writer, []string{"github.com/github/foo"})
assert.Len(t, writer.diagnostics, 1, "Expected one diagnostic to be emitted")
d := writer.diagnostics[0]
assert.Equal(t, d.Source.Id, "go/autobuilder/package-not-found")
assert.Equal(t, d.Severity, string(severityWarning))
assert.True(t, d.Visibility.CliSummaryTable)
assert.True(t, d.Visibility.StatusPage)
assert.True(t, d.Visibility.Telemetry)
// Non-Actions suggestion for private registries
assert.Contains(t, d.MarkdownMessage, "ensure that the necessary credentials and environment variables are set up")
// Custom build command suggestion
assert.Contains(t, d.MarkdownMessage, "If any of the packages are already present in the repository")
}
func Test_EmitCannotFindPackages_Dynamic(t *testing.T) {
writer := newMemoryDiagnosticsWriter()
// Set environment variables that affect the diagnostic message.
t.Setenv("GITHUB_EVENT_NAME", "dynamic")
t.Setenv("GITHUB_ACTIONS", "true")
EmitCannotFindPackages(writer, []string{"github.com/github/foo"})
assert.Len(t, writer.diagnostics, 1, "Expected one diagnostic to be emitted")
d := writer.diagnostics[0]
assert.Equal(t, d.Source.Id, "go/autobuilder/package-not-found")
assert.Equal(t, d.Severity, string(severityWarning))
// Dynamic workflow suggestion for private registries
assert.Contains(t, d.MarkdownMessage, "can grant access to private registries for GitHub security products")
// No default suggestions for private registries and custom build command
assert.NotContains(t, d.MarkdownMessage, "ensure that the necessary credentials and environment variables are set up")
assert.NotContains(t, d.MarkdownMessage, "If any of the packages are already present in the repository")
}
func Test_EmitCannotFindPackages_Actions(t *testing.T) {
writer := newMemoryDiagnosticsWriter()
// Set environment variables that affect the diagnostic message.
t.Setenv("GITHUB_EVENT_NAME", "push")
t.Setenv("GITHUB_ACTIONS", "true")
EmitCannotFindPackages(writer, []string{"github.com/github/foo"})
assert.Len(t, writer.diagnostics, 1, "Expected one diagnostic to be emitted")
d := writer.diagnostics[0]
assert.Equal(t, d.Source.Id, "go/autobuilder/package-not-found")
assert.Equal(t, d.Severity, string(severityWarning))
// Advanced workflow suggestion for private registries
assert.Contains(t, d.MarkdownMessage, "add a step to your workflow which sets up")
// No default suggestion for private registries
assert.NotContains(t, d.MarkdownMessage, "ensure that the necessary credentials and environment variables are set up")
// Custom build command suggestion
assert.Contains(t, d.MarkdownMessage, "If any of the packages are already present in the repository")
}

View File

@@ -223,7 +223,7 @@ func ExtractWithFlags(buildFlags []string, patterns []string, extractTests bool,
})
if len(pkgsNotFound) > 0 {
diagnostics.EmitCannotFindPackages(pkgsNotFound)
diagnostics.EmitCannotFindPackages(diagnostics.DefaultWriter, pkgsNotFound)
}
for _, pkg := range pkgs {

View File

@@ -13,4 +13,10 @@ require (
golang.org/x/tools v0.41.0
)
require golang.org/x/sync v0.19.0 // indirect
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.11.1 // indirect
golang.org/x/sync v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@@ -1,8 +1,17 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=