Add comprehensive CI/CD improvements and test coverage

This commit introduces major enhancements to the CI/CD pipeline and testing infrastructure:

CI/CD Improvements:
- Consolidated modern and legacy CI workflows into a single comprehensive pipeline
- Removed all publishing functionality from CI (no longer needed)
- Added 8 new advanced testing jobs for pull requests:
  * advanced-coverage: Detailed coverage analysis with base branch comparison
  * performance-profile: CPU and memory profiling with benchmarks
  * fuzz-test: Automated fuzz testing for supported packages
  * deep-analysis: Multiple static analysis tools (shadow, ineffassign, gosec, staticcheck)
  * mutation-test: Tests effectiveness of test suite on changed files
  * dependency-audit: Security vulnerabilities and outdated dependency checks
  * stress-test: Race detection with 100 iterations and parallel testing
  * test-report-summary: Aggregates all reports into a single PR comment
- Enabled RUN_LONG_TESTS by default for thorough testing
- Added automatic PR comment generation with all test results

Testing Infrastructure:
- Added comprehensive test files across all packages to improve coverage
- Implemented unit tests for previously untested packages
- Added race condition tests for concurrent operations
- Created integration tests for API endpoints
- Added storage backend tests (etcd, goleveldb)
- Implemented command-line interface tests

Local Testing Support:
- Added act configuration for testing GitHub Actions locally
- Created docker-compose.ci.yml for full CI environment simulation
- Updated CONTRIBUTING.md with detailed local testing instructions

Documentation Updates:
- Added comprehensive CI documentation to CONTRIBUTING.md
- Removed obsolete references to Travis CI
- Updated Go version requirements to 1.24
- Added act usage instructions and examples

Other Improvements:
- Updated .gitignore to exclude coverage reports and build artifacts
- Added test-act.yml workflow for testing act functionality
- Created CI_SUMMARY.md documenting all CI capabilities

These changes transform aptly's CI from a basic testing pipeline into a comprehensive quality assurance system that provides immediate feedback on code quality, performance, security, and test effectiveness.
This commit is contained in:
Nick Bozhenko
2025-07-10 12:00:54 -04:00
parent cd30723750
commit 40ba104838
80 changed files with 26961 additions and 275 deletions
+171
View File
@@ -0,0 +1,171 @@
package context
import (
"fmt"
"sync"
"testing"
"time"
"github.com/aptly-dev/aptly/aptly"
"github.com/aptly-dev/aptly/utils"
)
// Test for unsafe map access race condition
func TestPublishedStorageMapRace(t *testing.T) {
// Create a context with empty config
context := &AptlyContext{}
// publishedStorages is now sync.Map, initialized by zero value
// Mock config
utils.Config = utils.ConfigStructure{
RootDir: "/tmp/aptly-test",
FileSystemPublishRoots: map[string]utils.FileSystemPublishRoot{
"test": {RootDir: "/tmp/test", LinkMethod: "hardlink"},
},
}
var wg sync.WaitGroup
errors := make(chan error, 100)
// Simulate concurrent access to the same storage
for i := 0; i < 50; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
errors <- fmt.Errorf("panic in goroutine %d: %v", id, r)
}
}()
// All goroutines try to access the same storage
storage := context.GetPublishedStorage("filesystem:test")
if storage == nil {
errors <- fmt.Errorf("got nil storage in goroutine %d", id)
}
}(i)
}
// Also test different storages to trigger map growth
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
errors <- fmt.Errorf("panic in storage %d: %v", id, r)
}
}()
// Add new storage configurations
storageName := fmt.Sprintf("filesystem:test%d", id)
utils.Config.FileSystemPublishRoots[fmt.Sprintf("test%d", id)] = utils.FileSystemPublishRoot{
RootDir: fmt.Sprintf("/tmp/test%d", id),
LinkMethod: "hardlink",
}
storage := context.GetPublishedStorage(storageName)
if storage == nil {
errors <- fmt.Errorf("got nil storage for %s", storageName)
}
}(i)
}
wg.Wait()
close(errors)
// Check for any errors or panics
for err := range errors {
t.Errorf("Race condition error: %v", err)
}
}
// Test for concurrent map writes
func TestPublishedStorageConcurrentWrites(t *testing.T) {
context := &AptlyContext{}
utils.Config = utils.ConfigStructure{
RootDir: "/tmp/aptly-test",
FileSystemPublishRoots: make(map[string]utils.FileSystemPublishRoot),
}
var wg sync.WaitGroup
panics := make(chan string, 100)
// Multiple goroutines trying to create different storages simultaneously
for i := 0; i < 20; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
panics <- fmt.Sprintf("goroutine %d panicked: %v", id, r)
}
}()
storageName := fmt.Sprintf("filesystem:concurrent%d", id)
utils.Config.FileSystemPublishRoots[fmt.Sprintf("concurrent%d", id)] = utils.FileSystemPublishRoot{
RootDir: fmt.Sprintf("/tmp/concurrent%d", id),
LinkMethod: "hardlink",
}
// This should trigger concurrent map writes
_ = context.GetPublishedStorage(storageName)
// Add some delay to increase chance of race
time.Sleep(time.Millisecond)
// Access again to ensure consistency
storage2 := context.GetPublishedStorage(storageName)
if storage2 == nil {
panics <- fmt.Sprintf("inconsistent storage access in goroutine %d", id)
}
}(i)
}
wg.Wait()
close(panics)
// Check for panics (indicating race condition)
for panic := range panics {
t.Errorf("Concurrent map access issue: %s", panic)
}
}
// Test for storage initialization race
func TestPublishedStorageInitRace(t *testing.T) {
// Run this test multiple times to increase chance of catching race
for attempt := 0; attempt < 10; attempt++ {
context := &AptlyContext{}
utils.Config = utils.ConfigStructure{
RootDir: "/tmp/aptly-test",
FileSystemPublishRoots: map[string]utils.FileSystemPublishRoot{
"race": {RootDir: "/tmp/race", LinkMethod: "hardlink"},
},
}
var wg sync.WaitGroup
storages := make([]aptly.PublishedStorage, 10)
// Multiple goroutines accessing the same non-existent storage
for i := 0; i < 10; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
storages[idx] = context.GetPublishedStorage("filesystem:race")
}(i)
}
wg.Wait()
// All should get the same storage instance
firstStorage := storages[0]
for i := 1; i < len(storages); i++ {
if storages[i] != firstStorage {
t.Errorf("Attempt %d: Got different storage instances: race condition in initialization", attempt)
break
}
}
}
}