diff --git a/pkg/exec/command.go b/pkg/exec/command.go index bc8e913a0..35ac5b07b 100644 --- a/pkg/exec/command.go +++ b/pkg/exec/command.go @@ -18,6 +18,8 @@ const ( defaultTimeout = 30 * time.Second ) +var execCommandContext = exec.CommandContext + // RunHostCommandOptions provides configuration options for RunHostCommand type RunHostCommandOptions struct { // Timeout specifies the maximum duration for command execution @@ -78,7 +80,7 @@ func RunHostCommand(ctx context.Context, command string, args []string, opts *Ru nsenterArgs := buildNsenterArgs(opts, command, args) // Create command - cmd := exec.CommandContext(ctx, "nsenter", nsenterArgs...) + cmd := execCommandContext(ctx, "nsenter", nsenterArgs...) // Set working directory if specified if opts.WorkingDir != "" { diff --git a/pkg/exec/command_test.go b/pkg/exec/command_test.go index 19ffb33dd..dc4dcba2a 100644 --- a/pkg/exec/command_test.go +++ b/pkg/exec/command_test.go @@ -2,145 +2,196 @@ package exec import ( "context" - "runtime" + "fmt" + "os" + "os/exec" + "path/filepath" + "reflect" "strings" "testing" "time" ) -func TestRunHostCommand(t *testing.T) { - // Skip tests if not running on Linux - if runtime.GOOS != "linux" { - t.Skip("nsenter tests can only run on Linux") +const commandHelperEnv = "GO_WANT_COMMAND_HELPER" + +func TestCommandHelperProcess(t *testing.T) { + if os.Getenv(commandHelperEnv) != "1" { + return } - // Skip if nsenter is not available - if !IsNsenterAvailable() { - t.Skip("nsenter is not available on this system") + separator := -1 + for i, arg := range os.Args { + if arg == "--" { + separator = i + break + } + } + if separator == -1 || separator+1 >= len(os.Args) { + os.Exit(2) } - // Check if we have permission to use nsenter (requires root or CAP_SYS_ADMIN) - // Try a simple test command first - testResult, _ := RunHostCommand(context.Background(), "true", nil, nil) - if testResult != nil && testResult.Error != nil && strings.Contains(testResult.Stderr, "Permission denied") { - t.Skip("Insufficient permissions to run nsenter tests (need root or CAP_SYS_ADMIN)") + switch os.Args[separator+1] { + case "success": + fmt.Fprintln(os.Stdout, "helper stdout") + fmt.Fprintln(os.Stderr, "helper stderr") + os.Exit(0) + case "failure": + fmt.Fprintln(os.Stderr, "helper failure") + os.Exit(23) + case "sleep": + time.Sleep(10 * time.Second) + os.Exit(0) + default: + os.Exit(2) } +} - tests := []struct { - name string - command string - args []string - opts *RunHostCommandOptions - wantErr bool - check func(t *testing.T, result *CommandResult) - }{ - { - name: "simple echo command", - command: "echo", - args: []string{"hello", "world"}, - opts: nil, - wantErr: false, - check: func(t *testing.T, result *CommandResult) { - if result.ExitCode != 0 { - t.Errorf("expected exit code 0, got %d", result.ExitCode) - } - if !strings.Contains(result.Stdout, "hello world") { - t.Errorf("expected output to contain 'hello world', got %s", result.Stdout) - } - }, - }, - { - name: "command with timeout", - command: "sleep", - args: []string{"0.1"}, - opts: &RunHostCommandOptions{ - Timeout: 1 * time.Second, - }, - wantErr: false, - check: func(t *testing.T, result *CommandResult) { - if result.ExitCode != 0 { - t.Errorf("expected exit code 0, got %d", result.ExitCode) - } - }, - }, - { - name: "empty command", - command: "", - args: nil, - opts: nil, - wantErr: true, - check: nil, - }, - { - name: "non-existent command", - command: "this-command-does-not-exist", - args: nil, - opts: nil, - wantErr: false, - check: func(t *testing.T, result *CommandResult) { - if result.ExitCode == 0 { - t.Errorf("expected non-zero exit code for non-existent command") - } - }, - }, - { - name: "command with custom namespaces", - command: "pwd", - args: nil, - opts: &RunHostCommandOptions{ - Namespaces: []string{"pid", "net"}, - }, - wantErr: false, - check: func(t *testing.T, result *CommandResult) { - if result.ExitCode != 0 { - t.Errorf("expected exit code 0, got %d", result.ExitCode) - } - }, - }, +type commandInvocation struct { + name string + args []string +} + +func useCommandHelper(t *testing.T, behavior string) *commandInvocation { + t.Helper() + + originalExecCommand := execCommandContext + invocation := &commandInvocation{} + execCommandContext = func(ctx context.Context, name string, args ...string) *exec.Cmd { + invocation.name = name + invocation.args = append([]string(nil), args...) + + cmd := exec.CommandContext(ctx, os.Args[0], "-test.run=^TestCommandHelperProcess$", "--", behavior) + cmd.Env = append(os.Environ(), commandHelperEnv+"=1") + return cmd } + t.Cleanup(func() { + execCommandContext = originalExecCommand + }) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := RunHostCommand(context.Background(), tt.command, tt.args, tt.opts) + return invocation +} - if (err != nil) != tt.wantErr { - t.Errorf("RunHostCommand() error = %v, wantErr %v", err, tt.wantErr) - return - } +func TestRunHostCommand(t *testing.T) { + invocation := useCommandHelper(t, "success") - if tt.check != nil && result != nil { - tt.check(t, result) - } - }) + result, err := RunHostCommand(context.Background(), "echo", []string{"hello", "world"}, nil) + if err != nil { + t.Fatalf("RunHostCommand() error = %v", err) + } + if result.Error != nil { + t.Fatalf("RunHostCommand() command error = %v", result.Error) + } + if result.ExitCode != 0 { + t.Errorf("RunHostCommand() exit code = %d, want 0", result.ExitCode) + } + if result.Stdout != "helper stdout\n" { + t.Errorf("RunHostCommand() stdout = %q, want %q", result.Stdout, "helper stdout\n") + } + if result.Stderr != "helper stderr\n" { + t.Errorf("RunHostCommand() stderr = %q, want %q", result.Stderr, "helper stderr\n") + } + if invocation.name != "nsenter" { + t.Errorf("RunHostCommand() executable = %q, want %q", invocation.name, "nsenter") + } + wantArgs := []string{ + "--target", "1", + "--pid", "--mount", "--uts", "--ipc", "--net", + "--", + "echo", "hello", "world", + } + if !reflect.DeepEqual(invocation.args, wantArgs) { + t.Errorf("RunHostCommand() args = %q, want %q", invocation.args, wantArgs) } } -func TestRunHostCommandSimple(t *testing.T) { - // Skip tests if not running on Linux - if runtime.GOOS != "linux" { - t.Skip("nsenter tests can only run on Linux") +func TestRunHostCommandNonZeroExit(t *testing.T) { + useCommandHelper(t, "failure") + + result, err := RunHostCommand(context.Background(), "false", nil, nil) + if err != nil { + t.Fatalf("RunHostCommand() error = %v", err) } + if result.Error == nil { + t.Fatal("RunHostCommand() command error = nil, want non-nil") + } + if result.ExitCode != 23 { + t.Errorf("RunHostCommand() exit code = %d, want 23", result.ExitCode) + } + if result.Stderr != "helper failure\n" { + t.Errorf("RunHostCommand() stderr = %q, want %q", result.Stderr, "helper failure\n") + } +} - // Skip if nsenter is not available - if !IsNsenterAvailable() { - t.Skip("nsenter is not available on this system") +func TestRunHostCommandStartFailure(t *testing.T) { + originalExecCommand := execCommandContext + execCommandContext = func(ctx context.Context, _ string, _ ...string) *exec.Cmd { + return exec.CommandContext(ctx, filepath.Join(t.TempDir(), "missing-command")) } + t.Cleanup(func() { + execCommandContext = originalExecCommand + }) - // Check permissions - testResult, _ := RunHostCommand(context.Background(), "true", nil, nil) - if testResult != nil && testResult.Error != nil && strings.Contains(testResult.Stderr, "Permission denied") { - t.Skip("Insufficient permissions to run nsenter tests") + result, err := RunHostCommand(context.Background(), "echo", nil, nil) + if err != nil { + t.Fatalf("RunHostCommand() error = %v", err) } + if result.Error == nil { + t.Fatal("RunHostCommand() command error = nil, want non-nil") + } +} + +func TestRunHostCommandTimeout(t *testing.T) { + useCommandHelper(t, "sleep") + + start := time.Now() + result, err := RunHostCommand(context.Background(), "sleep", []string{"10"}, &RunHostCommandOptions{ + Timeout: 20 * time.Millisecond, + }) + if err != nil { + t.Fatalf("RunHostCommand() error = %v", err) + } + if result.Error == nil { + t.Fatal("RunHostCommand() command error = nil, want timeout error") + } + if elapsed := time.Since(start); elapsed >= time.Second { + t.Errorf("RunHostCommand() elapsed = %v, want less than 1s", elapsed) + } +} + +func TestRunHostCommandRejectsEmptyCommand(t *testing.T) { + result, err := RunHostCommand(context.Background(), "", nil, nil) + if err == nil { + t.Fatal("RunHostCommand() error = nil, want non-nil") + } + if result != nil { + t.Errorf("RunHostCommand() result = %#v, want nil", result) + } +} + +func TestRunHostCommandSimple(t *testing.T) { + useCommandHelper(t, "success") - // Test simple command execution output, err := RunHostCommandSimple("echo", "test") if err != nil { - t.Errorf("RunHostCommandSimple() error = %v", err) - return + t.Fatalf("RunHostCommandSimple() error = %v", err) } + if output != "helper stdout\n" { + t.Errorf("RunHostCommandSimple() = %q, want %q", output, "helper stdout\n") + } +} + +func TestRunHostCommandSimpleFailure(t *testing.T) { + useCommandHelper(t, "failure") - if !strings.Contains(output, "test") { - t.Errorf("expected output to contain 'test', got %s", output) + output, err := RunHostCommandSimple("false") + if err == nil { + t.Fatal("RunHostCommandSimple() error = nil, want non-nil") + } + if output != "" { + t.Errorf("RunHostCommandSimple() output = %q, want empty", output) + } + if !strings.Contains(err.Error(), "helper failure") { + t.Errorf("RunHostCommandSimple() error = %q, want helper stderr", err) } } diff --git a/pkg/gnoi/file/file_test.go b/pkg/gnoi/file/file_test.go index 071e030bd..95809af62 100644 --- a/pkg/gnoi/file/file_test.go +++ b/pkg/gnoi/file/file_test.go @@ -26,6 +26,12 @@ import ( "google.golang.org/grpc/status" ) +func transferTestPath(t *testing.T, name string) (logical, physical string) { + t.Helper() + logicalDir, physicalDir := useTempHostRoot(t) + return filepath.Join(logicalDir, name), filepath.Join(physicalDir, name) +} + func TestHandleTransferToRemote_Success(t *testing.T) { // Create test HTTP server testContent := []byte("test firmware content") @@ -35,9 +41,7 @@ func TestHandleTransferToRemote_Success(t *testing.T) { })) defer server.Close() - // Create temp directory for output - tempDir := t.TempDir() - localPath := filepath.Join(tempDir, "firmware.bin") + localPath, physicalPath := transferTestPath(t, "firmware.bin") // Create request req := &gnoi_file_pb.TransferToRemoteRequest{ @@ -69,7 +73,7 @@ func TestHandleTransferToRemote_Success(t *testing.T) { } // Verify file was downloaded - content, err := os.ReadFile(localPath) + content, err := os.ReadFile(physicalPath) if err != nil { t.Fatalf("Failed to read downloaded file: %v", err) } @@ -138,9 +142,9 @@ func TestHandleTransferToRemote_EmptyLocalPath(t *testing.T) { } func TestHandleTransferToRemote_EmptyURL(t *testing.T) { - tempDir := t.TempDir() + localPath, _ := transferTestPath(t, "test.bin") req := &gnoi_file_pb.TransferToRemoteRequest{ - LocalPath: filepath.Join(tempDir, "test.bin"), + LocalPath: localPath, RemoteDownload: &common.RemoteDownload{ Path: "", Protocol: common.RemoteDownload_HTTP, @@ -160,9 +164,9 @@ func TestHandleTransferToRemote_EmptyURL(t *testing.T) { } func TestHandleTransferToRemote_UnsupportedProtocol(t *testing.T) { - tempDir := t.TempDir() + localPath, _ := transferTestPath(t, "test.bin") req := &gnoi_file_pb.TransferToRemoteRequest{ - LocalPath: filepath.Join(tempDir, "test.bin"), + LocalPath: localPath, RemoteDownload: &common.RemoteDownload{ Path: "https://example.com/file", Protocol: common.RemoteDownload_HTTPS, @@ -188,9 +192,9 @@ func TestHandleTransferToRemote_DownloadFails(t *testing.T) { })) defer server.Close() - tempDir := t.TempDir() + localPath, _ := transferTestPath(t, "test.bin") req := &gnoi_file_pb.TransferToRemoteRequest{ - LocalPath: filepath.Join(tempDir, "test.bin"), + LocalPath: localPath, RemoteDownload: &common.RemoteDownload{ Path: server.URL, Protocol: common.RemoteDownload_HTTP, @@ -220,8 +224,7 @@ func TestHandleTransferToRemote_HashVerification(t *testing.T) { })) defer server.Close() - tempDir := t.TempDir() - localPath := filepath.Join(tempDir, "test.txt") + localPath, _ := transferTestPath(t, "test.txt") req := &gnoi_file_pb.TransferToRemoteRequest{ LocalPath: localPath, @@ -253,9 +256,7 @@ func TestHandleTransferToRemote_NestedDirectories(t *testing.T) { })) defer server.Close() - tempDir := t.TempDir() - // Path with nested directories - localPath := filepath.Join(tempDir, "a", "b", "c", "file.bin") + localPath, physicalPath := transferTestPath(t, filepath.Join("a", "b", "c", "file.bin")) req := &gnoi_file_pb.TransferToRemoteRequest{ LocalPath: localPath, @@ -272,7 +273,7 @@ func TestHandleTransferToRemote_NestedDirectories(t *testing.T) { } // Verify file was created - if _, err := os.Stat(localPath); os.IsNotExist(err) { + if _, err := os.Stat(physicalPath); os.IsNotExist(err) { t.Error("File was not created in nested directory") } } @@ -327,9 +328,9 @@ func TestHandleTransferToRemote_ContextCancellation(t *testing.T) { })) defer server.Close() - tempDir := t.TempDir() + localPath, _ := transferTestPath(t, "test.bin") req := &gnoi_file_pb.TransferToRemoteRequest{ - LocalPath: filepath.Join(tempDir, "test.bin"), + LocalPath: localPath, RemoteDownload: &common.RemoteDownload{ Path: server.URL, Protocol: common.RemoteDownload_HTTP, @@ -1455,9 +1456,9 @@ func TestHandleTransferToRemote_EdgeCases(t *testing.T) { })) defer server.Close() - tempDir := t.TempDir() + localPath, _ := transferTestPath(t, "large.bin") req := &gnoi_file_pb.TransferToRemoteRequest{ - LocalPath: filepath.Join(tempDir, "large.bin"), + LocalPath: localPath, RemoteDownload: &common.RemoteDownload{ Path: server.URL, Protocol: common.RemoteDownload_HTTP, @@ -1484,9 +1485,9 @@ func TestHandleTransferToRemote_EdgeCases(t *testing.T) { })) defer slowServer.Close() - tempDir := t.TempDir() + localPath, _ := transferTestPath(t, "timeout.bin") req := &gnoi_file_pb.TransferToRemoteRequest{ - LocalPath: filepath.Join(tempDir, "timeout.bin"), + LocalPath: localPath, RemoteDownload: &common.RemoteDownload{ Path: slowServer.URL, Protocol: common.RemoteDownload_HTTP, @@ -1854,9 +1855,9 @@ func TestHandleTransferToRemote_HighCoverage(t *testing.T) { })) defer server.Close() - tempDir := t.TempDir() + localPath, _ := transferTestPath(t, "cleanup_test.bin") req := &gnoi_file_pb.TransferToRemoteRequest{ - LocalPath: filepath.Join(tempDir, "cleanup_test.bin"), + LocalPath: localPath, RemoteDownload: &common.RemoteDownload{ Path: server.URL, Protocol: common.RemoteDownload_HTTP, diff --git a/pure.mk b/pure.mk index 39d359357..2af6ed6f8 100644 --- a/pure.mk +++ b/pure.mk @@ -1,44 +1,22 @@ -# pure.mk - Simple CI for pure packages without SONiC dependencies +# pure.mk - Simple CI for canonical Go packages without SONiC dependencies # Usage: make -f pure.mk ci # -# This makefile supports testing packages that don't require CGO or SONiC dependencies. -# Add new pure packages to PURE_PACKAGES below. -# -# Goal: Eventually all packages should be pure unless they absolutely -# require CGO dependencies. All CGO/SONiC dependencies should be properly quarantined. +# Every Go package under internal/, pkg/, and cmd/ must remain pure. Packages +# outside those canonical roots may depend on the SONiC build environment. # Go configuration GO ?= go GOROOT ?= $(shell $(GO) env GOROOT) -# Pure packages (no CGO/SONiC dependencies) -# Add new packages here as they become pure-compatible. -PURE_PACKAGES := \ - internal/exec \ - pkg/gnoi/debug \ - pkg/bypass \ - internal/diskspace \ - internal/hash \ - internal/download \ - internal/firmware \ - pkg/interceptors \ - pkg/server/operational-handler \ - pkg/gnoi/file \ - pkg/exec \ - pkg/gnoi/os \ - pkg/gnoi/oras \ - pkg/hostfs \ - pkg/gnoi/system - -# Future packages to make pure: -# TODO: sonic-gnmi-standalone/pkg/workflow -# TODO: sonic-gnmi-standalone/pkg/client/config -# TODO: sonic-gnmi-standalone/internal/checksum -# TODO: sonic-gnmi-standalone/internal/download -# TODO: common_utils (parts that don't need CGO) -# TODO: gnoi_client/config -# TODO: transl_utils (isolate from translib dependencies) -# TODO: pkg/interceptors/dpuproxy (needs gRPC infrastructure mocking) +# Discover every package under the canonical pure roots. This makes purity a +# path-based invariant instead of an allowlist that can omit new packages. +PURE_ROOTS := internal pkg cmd +PURE_PACKAGES := $(shell \ + for root in $(PURE_ROOTS); do \ + if [ -d "$$root" ]; then \ + find "$$root" -type f -name '*.go' -print; \ + fi; \ + done | sed 's|/[^/]*$$||' | sort -u) # You can test specific packages by setting PACKAGES=pkg/specific/package PACKAGES ?= $(PURE_PACKAGES) @@ -51,7 +29,7 @@ PACKAGES ?= $(PURE_PACKAGES) clean: @echo "Cleaning pure build artifacts..." $(GO) clean -cache -testcache - @for pkg in $(PACKAGES); do \ + @set -e; for pkg in $(PACKAGES); do \ rm -f $$pkg/coverage.out $$pkg/coverage.html; \ done @@ -59,9 +37,9 @@ clean: .PHONY: fmt-check fmt-check: @echo "Checking Go code formatting for pure packages..." - @for pkg in $(PACKAGES); do \ + @set -e; for pkg in $(PACKAGES); do \ echo "Checking $$pkg..."; \ - files=$$($(GOROOT)/bin/gofmt -l $$pkg/*.go 2>/dev/null || true); \ + files=$$($(GOROOT)/bin/gofmt -l $$pkg/*.go); \ if [ -n "$$files" ]; then \ echo "The following files need formatting in $$pkg:"; \ echo "$$files"; \ @@ -75,41 +53,39 @@ fmt-check: .PHONY: fmt fmt: @echo "Formatting Go code for pure packages..." - @for pkg in $(PACKAGES); do \ + @set -e; for pkg in $(PACKAGES); do \ echo "Formatting $$pkg..."; \ - $(GOROOT)/bin/gofmt -w $$pkg/*.go 2>/dev/null || true; \ + $(GOROOT)/bin/gofmt -w $$pkg/*.go; \ done # Vet - static analysis .PHONY: vet vet: @echo "Running go vet on pure packages..." - @for pkg in $(PACKAGES); do \ + @set -e; for pkg in $(PACKAGES); do \ echo "Vetting $$pkg..."; \ - cd $$pkg && $(GO) vet ./...; \ - cd - >/dev/null; \ + (cd $$pkg && $(GO) vet .); \ done # Test - run all tests with coverage .PHONY: test test: @echo "Running tests for pure packages..." - @for pkg in $(PACKAGES); do \ + @set -e; for pkg in $(PACKAGES); do \ echo ""; \ echo "=== Testing $$pkg ==="; \ - cd $$pkg && $(GO) test -gcflags="all=-N -l" -v -race -coverprofile=coverage.out -covermode=atomic ./...; \ - if [ -f coverage.out ]; then \ + (cd $$pkg && $(GO) test -gcflags="all=-N -l" -v -race -coverprofile=coverage.out -covermode=atomic .); \ + if [ -f $$pkg/coverage.out ]; then \ echo "Coverage for $$pkg:"; \ - $(GO) tool cover -func=coverage.out; \ + (cd $$pkg && $(GO) tool cover -func=coverage.out); \ fi; \ - cd - >/dev/null; \ done # Generate coverage files for Azure pipeline integration .PHONY: azure-coverage azure-coverage: @echo "Generating coverage files for Azure pipeline..." - @for pkg in $(PACKAGES); do \ + @set -e; for pkg in $(PACKAGES); do \ echo "Testing $$pkg..."; \ pkgname=$$(echo $$pkg | tr '/' '-'); \ $(GO) test -gcflags="all=-N -l" -race -coverprofile=coverage-pure-$$pkgname.txt -covermode=atomic -v ./$$pkg; \ @@ -120,12 +96,11 @@ azure-coverage: .PHONY: test-coverage test-coverage: test @echo "Generating HTML coverage reports..." - @for pkg in $(PACKAGES); do \ + @set -e; for pkg in $(PACKAGES); do \ if [ -f $$pkg/coverage.out ]; then \ echo "Generating coverage report for $$pkg..."; \ - cd $$pkg && $(GO) tool cover -html=coverage.out -o coverage.html; \ + (cd $$pkg && $(GO) tool cover -html=coverage.out -o coverage.html); \ echo "Coverage report generated: $$pkg/coverage.html"; \ - cd - >/dev/null; \ fi; \ done @@ -133,7 +108,7 @@ test-coverage: test .PHONY: coverage-xml coverage-xml: test @echo "Generating XML coverage report for Azure..." - @if command -v gocov >/dev/null 2>&1 && command -v gocov-xml >/dev/null 2>&1; then \ + @set -e; if command -v gocov >/dev/null 2>&1 && command -v gocov-xml >/dev/null 2>&1; then \ echo "Converting coverage to XML format..."; \ rm -f coverage-*.out; \ for pkg in $(PACKAGES); do \ @@ -143,8 +118,9 @@ coverage-xml: test fi; \ done; \ if ls coverage-*.out >/dev/null 2>&1; then \ - gocov convert coverage-*.out | gocov-xml -source $(shell pwd) > coverage.xml; \ - rm -f coverage-*.out; \ + trap 'rm -f coverage-*.out coverage-pure.json' EXIT; \ + gocov convert coverage-*.out > coverage-pure.json; \ + gocov-xml -source $(shell pwd) < coverage-pure.json > coverage.xml; \ echo "XML coverage report generated: coverage.xml"; \ else \ echo "No coverage files found"; \ @@ -159,10 +135,9 @@ coverage-xml: test .PHONY: build-test build-test: @echo "Testing build of pure packages..." - @for pkg in $(PACKAGES); do \ + @set -e; for pkg in $(PACKAGES); do \ echo "Building $$pkg..."; \ - cd $$pkg && $(GO) build -v ./...; \ - cd - >/dev/null; \ + (cd $$pkg && $(GO) build -v .); \ done # Lint check using basic go tools @@ -174,10 +149,9 @@ lint: fmt-check .PHONY: bench bench: @echo "Running benchmarks for pure packages..." - @for pkg in $(PACKAGES); do \ + @set -e; for pkg in $(PACKAGES); do \ echo "Benchmarking $$pkg..."; \ - cd $$pkg && $(GO) test -bench=. -benchmem ./...; \ - cd - >/dev/null; \ + (cd $$pkg && $(GO) test -bench=. -benchmem .); \ done # Module verification @@ -196,11 +170,10 @@ mod-verify: .PHONY: security security: @echo "Running security scan on pure packages..." - @if command -v gosec >/dev/null 2>&1; then \ + @set -e; if command -v gosec >/dev/null 2>&1; then \ for pkg in $(PACKAGES); do \ echo "Scanning $$pkg..."; \ - cd $$pkg && gosec ./...; \ - cd - >/dev/null; \ + (cd $$pkg && gosec .); \ done; \ else \ echo "gosec not available, skipping security scan"; \ @@ -263,7 +236,8 @@ junit-xml: clean @export PATH=$(PATH):$(shell $(GO) env GOPATH)/bin && \ gotestsum --junitfile test-results/junit-pure.xml \ --format testname \ - -- -v -race -coverprofile=test-results/coverage-pure.txt \ + -- -gcflags="all=-N -l" -v -race \ + -coverprofile=test-results/coverage-pure.txt \ -covermode=atomic \ $(addprefix ./,$(PACKAGES)) @echo "Converting coverage to Cobertura XML format..."