diff --git a/Makefile b/Makefile index 4f5858b72..09c5d7d28 100644 --- a/Makefile +++ b/Makefile @@ -274,7 +274,7 @@ envtest: .PHONY: unit-tests unit-tests: envtest KUBEBUILDER_ASSETS="$(shell ./operator/bin/setup-envtest use $(ENVTEST_K8S_VERSION) --bin-dir $(shell pwd)/operator/bin -p path)" \ - go test -tags $(GO_BUILD_TAGS) -v ./pkg/... ./cmd/... ./web/... ./pkg-new/... + go test -race -tags $(GO_BUILD_TAGS) -v ./pkg/... ./cmd/... ./web/... ./pkg-new/... $(MAKE) -C api unit-tests $(MAKE) -C operator test $(MAKE) -C utils unit-tests diff --git a/api/Makefile b/api/Makefile index dda8819ad..6b0bdc047 100644 --- a/api/Makefile +++ b/api/Makefile @@ -13,4 +13,4 @@ swag: .PHONY: unit-tests unit-tests: - go test -tags $(GO_BUILD_TAGS) -v ./... + go test -race -tags $(GO_BUILD_TAGS) -v ./... diff --git a/api/integration/install_test.go b/api/integration/install_test.go index fd03daf38..0dc9aca2c 100644 --- a/api/integration/install_test.go +++ b/api/integration/install_test.go @@ -794,11 +794,15 @@ func TestSetInstallStatus(t *testing.T) { func TestInstallWithAPIClient(t *testing.T) { password := "test-password" + // Create a runtimeconfig to be used in the install process + rc := runtimeconfig.New(nil) + // Create a config manager - installationManager := installation.NewInstallationManager() + installationManager := installation.NewInstallationManager(installation.WithRuntimeConfig(rc)) // Create an install controller with the config manager installController, err := install.NewInstallController( + install.WithRuntimeConfig(rc), install.WithInstallationManager(installationManager), ) require.NoError(t, err) diff --git a/api/internal/managers/installation/store_test.go b/api/internal/managers/installation/store_test.go new file mode 100644 index 000000000..3eca603d0 --- /dev/null +++ b/api/internal/managers/installation/store_test.go @@ -0,0 +1,167 @@ +package installation + +import ( + "sync" + "testing" + + "github.com/replicatedhq/embedded-cluster/api/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewMemoryStore(t *testing.T) { + inst := types.NewInstallation() + store := NewMemoryStore(inst) + + assert.NotNil(t, store) + assert.NotNil(t, store.installation) + assert.Equal(t, inst, store.installation) +} + +func TestMemoryStore_GetConfig(t *testing.T) { + inst := &types.Installation{ + Config: &types.InstallationConfig{ + AdminConsolePort: 8080, + DataDirectory: "/some/dir", + }, + } + store := NewMemoryStore(inst) + + config, err := store.GetConfig() + + require.NoError(t, err) + assert.NotNil(t, config) + assert.Equal(t, &types.InstallationConfig{ + AdminConsolePort: 8080, + DataDirectory: "/some/dir", + }, config) +} + +func TestMemoryStore_SetConfig(t *testing.T) { + inst := &types.Installation{ + Config: &types.InstallationConfig{ + AdminConsolePort: 1000, + DataDirectory: "/a/different/dir", + }, + } + store := NewMemoryStore(inst) + expectedConfig := types.InstallationConfig{ + AdminConsolePort: 8080, + DataDirectory: "/some/dir", + } + + err := store.SetConfig(expectedConfig) + + require.NoError(t, err) + + // Verify the config was stored + actualConfig, err := store.GetConfig() + require.NoError(t, err) + assert.Equal(t, &expectedConfig, actualConfig) +} + +func TestMemoryStore_GetStatus(t *testing.T) { + inst := &types.Installation{ + Status: &types.Status{ + State: "failed", + Description: "Failure", + }, + } + store := NewMemoryStore(inst) + + status, err := store.GetStatus() + + require.NoError(t, err) + assert.NotNil(t, status) + assert.Equal(t, &types.Status{ + State: "failed", + Description: "Failure", + }, status) +} +func TestMemoryStore_SetStatus(t *testing.T) { + inst := &types.Installation{ + Status: &types.Status{ + State: "failed", + Description: "Failure", + }, + } + store := NewMemoryStore(inst) + expectedStatus := types.Status{ + State: "running", + Description: "Running", + } + + err := store.SetStatus(expectedStatus) + + require.NoError(t, err) + + // Verify the status was stored + actualStatus, err := store.GetStatus() + require.NoError(t, err) + assert.Equal(t, &expectedStatus, actualStatus) +} + +// Useful to test concurrent access with -race flag +func TestMemoryStore_ConcurrentAccess(t *testing.T) { + inst := types.NewInstallation() + store := NewMemoryStore(inst) + var wg sync.WaitGroup + + // Test concurrent reads and writes + numGoroutines := 10 + numOperations := 50 + + // Concurrent config operations + wg.Add(numGoroutines * 2) + for i := 0; i < numGoroutines; i++ { + // Concurrent writes + go func(id int) { + defer wg.Done() + for j := 0; j < numOperations; j++ { + config := types.InstallationConfig{ + LocalArtifactMirrorPort: 8080, + DataDirectory: "/some/other/dir", + } + err := store.SetConfig(config) + assert.NoError(t, err) + } + }(i) + + // Concurrent reads + go func(id int) { + defer wg.Done() + for j := 0; j < numOperations; j++ { + _, err := store.GetConfig() + assert.NoError(t, err) + } + }(i) + } + + // Concurrent status operations + wg.Add(numGoroutines * 2) + for i := 0; i < numGoroutines; i++ { + // Concurrent writes + go func(id int) { + defer wg.Done() + for j := 0; j < numOperations; j++ { + status := types.Status{ + State: "pending", + Description: "Pending", + } + err := store.SetStatus(status) + assert.NoError(t, err) + } + }(i) + + // Concurrent reads + go func(id int) { + defer wg.Done() + for j := 0; j < numOperations; j++ { + _, err := store.GetStatus() + assert.NoError(t, err) + } + }(i) + } + + wg.Wait() +} diff --git a/cmd/installer/cli/signal_test.go b/cmd/installer/cli/signal_test.go index d2c81c129..a490f7024 100644 --- a/cmd/installer/cli/signal_test.go +++ b/cmd/installer/cli/signal_test.go @@ -4,6 +4,7 @@ import ( "context" "os" "sync" + "sync/atomic" "syscall" "testing" "time" @@ -37,9 +38,9 @@ func Test_signalHandler_Signal(t *testing.T) { originalOsExit := osExit defer func() { osExit = originalOsExit }() - exitCode := 0 + exitCode := int32(0) osExit = func(code int) { - exitCode = code + atomic.StoreInt32(&exitCode, int32(code)) // Instead of exiting, just cancel the context cancel() } @@ -76,7 +77,7 @@ func Test_signalHandler_Signal(t *testing.T) { // Verify cleanup was called with the expected error assert.True(t, cleanupCalled, "Cleanup function should have been called") assert.Equal(t, syscall.SIGINT, cleanupSignal, "Cleanup should be called with SIGINT") - assert.Equal(t, 1, exitCode, "Exit code should be 1") + assert.Equal(t, int32(1), atomic.LoadInt32(&exitCode), "Exit code should be 1") } func Test_signalHandler_ContextDone(t *testing.T) {