This document describes the testing infrastructure and continuous integration/continuous deployment (CI/CD) workflows for AKS Flex Node.
The project uses GitHub Actions for automated testing on pull requests and pushes to main/dev branches. The testing infrastructure includes:
- Build verification across Go 1.24
- Unit tests with race detection and coverage reporting
- Code quality checks with multiple linters
- Security scanning with gosec
- Dependency review for vulnerabilities
This workflow runs automatically on:
- Pull requests to
mainordevbranches - Direct pushes to
mainordevbranches
Jobs:
-
Build - Verifies the project builds successfully
- Tests on Go 1.24
- Builds for current platform and all supported platforms (linux/amd64, linux/arm64)
-
Test - Runs the test suite
- Executes all tests with race detection
- Generates coverage report
- Reports coverage percentage (warns if below 30% but doesn't fail)
-
Lint - Runs golangci-lint with comprehensive checks
- Uses
.golangci.ymlconfiguration - Checks code quality and common issues
- Uses
-
Security - Scans for security vulnerabilities
- Runs gosec security scanner
- Uploads results to GitHub Security tab
-
Code Quality - Additional quality checks
- Verifies code formatting with
gofmt - Verifies import formatting with
goimports - Runs
go vetfor correctness - Runs
staticcheckfor additional static analysis
- Verifies code formatting with
-
Dependency Review - Reviews dependencies for security issues
- Only runs on pull requests
- Fails on moderate or higher severity vulnerabilities
# Run all tests
make test
# Run tests with coverage report
make test-coverage
# Opens coverage.html in browser after generation
# Run tests with race detector
make test-race
# Run specific package tests
go test ./pkg/config/
go test ./pkg/logger/# Format code
make fmt
# Format imports
make fmt-imports
# Format both code and imports
make fmt-all
# Run go vet
make vet
# Run linter
make lint
# Run all quality checks (fmt-all + vet + lint + test)
make check
# Verify and tidy dependencies
make verifyThe project uses golangci-lint v2. If you don't have it installed:
# Linux/macOS (installs latest version)
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin
# macOS with Homebrew
brew install golangci-lint
# Or use Go install
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latestThe project uses .golangci.yml (v2 format) for linter configuration with the following enabled checks:
Enabled Linters:
- errcheck - Checks for unchecked errors (with
check-blank: falseto allow_ =in defer) - govet - Reports suspicious constructs
- ineffassign - Detects ineffectual assignments
- staticcheck - Advanced static analysis (includes gosimple checks)
- unused - Finds unused code
Exclusions:
- Test files (
_test.go) are excluded from errcheck to allow testing error conditions
The project enforces a minimum test coverage threshold of 30%. To view detailed coverage:
make test-coverage
# Opens coverage.html showing line-by-line coverageCoverage reports are uploaded as artifacts in GitHub Actions runs for review.
Before submitting a PR, run:
# Run all checks
make check
# Verify build works
make build-all
# Check dependencies
make verifyOr run the full suite:
make verify && make check && make build-all- Developer opens PR
- GitHub Actions automatically runs all checks
- All jobs must pass (green) before merge
- Reviews are conducted
- PR is merged to target branch
Recommended branch protection rules for main and dev:
- ✅ Require pull request reviews before merging
- ✅ Require status checks to pass before merging
- Build (Go 1.23)
- Build (Go 1.24)
- Test
- Lint
- Security
- Code Quality
- ✅ Require branches to be up to date before merging
- ✅ Require conversation resolution before merging
- Test files end with
_test.go - Place tests in the same package as the code being tested
- Use table-driven tests for multiple test cases
- Use subtests with
t.Run()for better organization
func TestFunctionName(t *testing.T) {
tests := []struct {
name string
input string
want string
wantErr bool
}{
{
name: "valid input",
input: "test",
want: "expected",
wantErr: false,
},
// more test cases...
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FunctionName(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("FunctionName() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("FunctionName() = %v, want %v", got, tt.want)
}
})
}
}- Test behavior, not implementation - Focus on what the code does, not how
- Use meaningful test names - Describe what is being tested
- Keep tests simple - Each test should verify one thing
- Mock external dependencies - Use interfaces for testability
- Test edge cases - Include boundary conditions and error cases
- Use test fixtures - Keep test data organized and reusable
If tests fail in CI but pass locally:
- Check Go version matches CI (1.23)
- Run with race detector:
make test-race - Check for environment-specific issues
- Ensure dependencies are up to date:
make verify
If linter fails in CI but passes locally:
- Ensure golangci-lint version matches CI (latest)
- Run:
make lint - Check
.golangci.ymlfor configuration - Some issues may be platform-specific
If coverage drops below 30%:
- Add tests for new code
- Focus on critical paths first
- Review
coverage.htmlfor uncovered lines - Consider raising threshold as coverage improves
Potential improvements to the testing infrastructure:
- Integration tests with actual Arc registration (requires Azure credentials)
- Performance benchmarks
- E2E tests in containerized environment
- Automated release notes generation
- Code coverage trending over time