From b5072c7da9d55a67974baf34eb918188450ced64 Mon Sep 17 00:00:00 2001 From: Crash-- Date: Sun, 10 May 2026 21:16:41 +0200 Subject: [PATCH 1/2] test(rabbitmq): bootstrap global CouchDB views in TestMain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests in this package call lifecycle.Create / instance.Get directly without going through testutils.NewSetup → GetTestInstance → stack.Start, so they never trigger couchdb.InitGlobalDB themselves. They've historically relied on the side effect of earlier model/* test packages bootstrapping the global DB and on the design doc persisting in the shared CouchDB service across test binaries. Go's test result cache (persisted via actions/setup-go cache) can let those packages be skipped, breaking the implicit dependency. The CI flake on PR #4716 manifested as TestSyncCreatedOrgContact failing with "CouchDB(not_found): missing" because instance.Service.Get queried _design/domain-and-aliases on a global instances DB where that design doc had never been created. A more robust fix would be to make instance.Service.Get treat any CouchDB "not_found" as ErrNotFound (it currently only handles no_db_file / "Database does not exist."). That would remove the implicit dependency for every package, not just this one. The repercussions on other Get callers haven't been fully audited yet, so this localized bootstrap stays in place until the broader change is vetted. Co-Authored-By: Claude Opus 4.7 (1M context) --- pkg/rabbitmq/main_test.go | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 pkg/rabbitmq/main_test.go diff --git a/pkg/rabbitmq/main_test.go b/pkg/rabbitmq/main_test.go new file mode 100644 index 00000000000..8d3edb10f85 --- /dev/null +++ b/pkg/rabbitmq/main_test.go @@ -0,0 +1,74 @@ +package rabbitmq_test + +import ( + "context" + "log" + "os" + "strings" + "testing" + + "github.com/cozy/cozy-stack/pkg/config/config" + "github.com/cozy/cozy-stack/pkg/couchdb" + "github.com/spf13/viper" +) + +// TestMain bootstraps the views and indexes that production stacks create +// at startup via stack.Start → couchdb.InitGlobalDB. Tests in this package +// call lifecycle.Create / instance.Get directly without going through +// testutils.NewSetup → GetTestInstance → stack.Start, so they never trigger +// that init themselves. +// +// They've historically relied on the side effect that earlier model/* test +// packages bootstrap the global DB and that the design doc persists in the +// shared CouchDB service across test binaries — but Go's test result cache +// can let those packages be skipped (cached "ok"), and then this implicit +// dependency breaks. See the CI flake debugged on PR #4716: the second +// lifecycle.Create call inside TestSyncCreatedOrgContact failed with +// "CouchDB(not_found): missing" because instance.Service.Get queried +// _design/domain-and-aliases on the global instances DB and that design +// doc had never been created. +// +// TODO: the more robust fix is to make instance.Service.Get treat any +// CouchDB "not_found" as ErrNotFound (it currently only handles +// no_db_file / "Database does not exist."). That would remove the implicit +// dependency for every package, not just this one. The repercussions on +// other Get callers haven't been fully audited though, so this localized +// bootstrap stays in place until that broader change is vetted. +func TestMain(m *testing.M) { + if err := loadTestConfigForMain(); err != nil { + log.Fatalf("rabbitmq tests: could not load test config: %s", err) + } + // Best-effort: if CouchDB is unreachable, individual tests that need it + // will fail via testutils.NeedCouchdb(t). We don't want TestMain itself + // to hard-fail in environments where CouchDB is intentionally absent. + if _, err := couchdb.CheckStatus(context.Background()); err == nil { + if err := couchdb.InitGlobalDB(context.Background()); err != nil { + log.Printf("rabbitmq tests: could not init global CouchDB: %s", err) + } + } + os.Exit(m.Run()) +} + +// loadTestConfigForMain mirrors config.UseTestFile but without the +// *testing.T plumbing so it can be called from TestMain. +func loadTestConfigForMain() error { + v := viper.New() + v.SetConfigName("cozy.test") + v.AddConfigPath("$HOME/.cozy") + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + v.SetEnvPrefix("cozy") + v.AutomaticEnv() + v.SetDefault("host", "localhost") + v.SetDefault("port", 8080) + v.SetDefault("assets", "./assets") + v.SetDefault("subdomains", "nested") + v.SetDefault("fs.url", "mem://test") + v.SetDefault("couchdb.url", "http://localhost:5984/") + v.SetDefault("log.level", "info") + if err := v.ReadInConfig(); err != nil { + if _, isMissing := err.(viper.ConfigFileNotFoundError); !isMissing { + return err + } + } + return config.UseViper(v) +} From 18cea6603355e85bb9e413a867bddefd589435ba Mon Sep 17 00:00:00 2001 From: Anton Shepilov Date: Fri, 29 May 2026 15:59:03 +0200 Subject: [PATCH 2/2] test: share CouchDB TestMain bootstrap --- pkg/config/config/config.go | 14 +++++--- pkg/rabbitmq/main_test.go | 66 ++--------------------------------- tests/testutils/test_utils.go | 19 ++++++++++ 3 files changed, 31 insertions(+), 68 deletions(-) diff --git a/pkg/config/config/config.go b/pkg/config/config/config.go index 9614176108c..2127e6f984b 100644 --- a/pkg/config/config/config.go +++ b/pkg/config/config/config.go @@ -1373,6 +1373,14 @@ func createTestViper() *viper.Viper { func UseTestFile(t *testing.T) { t.Helper() + if err := LoadTestFile(); err != nil { + t.Fatalf("fatal error test config file: %s", err) + } +} + +// LoadTestFile injects the standard test configuration without requiring +// testing.T. It is useful from TestMain implementations. +func LoadTestFile() error { build.BuildMode = build.ModeProd v := createTestViper() @@ -1380,13 +1388,11 @@ func UseTestFile(t *testing.T) { if _, ok := err.(viper.ConfigFileNotFoundError); ok { v = createTestViper() } else { - t.Fatalf("fatal error test config file: %s", err) + return err } } - if err := UseViper(v); err != nil { - t.Fatalf("fatal error test config file: %s", err) - } + return UseViper(v) } // FindConfigFile search in the Paths directories for the file with the given diff --git a/pkg/rabbitmq/main_test.go b/pkg/rabbitmq/main_test.go index 8d3edb10f85..9244bda10ee 100644 --- a/pkg/rabbitmq/main_test.go +++ b/pkg/rabbitmq/main_test.go @@ -1,74 +1,12 @@ package rabbitmq_test import ( - "context" - "log" "os" - "strings" "testing" - "github.com/cozy/cozy-stack/pkg/config/config" - "github.com/cozy/cozy-stack/pkg/couchdb" - "github.com/spf13/viper" + "github.com/cozy/cozy-stack/tests/testutils" ) -// TestMain bootstraps the views and indexes that production stacks create -// at startup via stack.Start → couchdb.InitGlobalDB. Tests in this package -// call lifecycle.Create / instance.Get directly without going through -// testutils.NewSetup → GetTestInstance → stack.Start, so they never trigger -// that init themselves. -// -// They've historically relied on the side effect that earlier model/* test -// packages bootstrap the global DB and that the design doc persists in the -// shared CouchDB service across test binaries — but Go's test result cache -// can let those packages be skipped (cached "ok"), and then this implicit -// dependency breaks. See the CI flake debugged on PR #4716: the second -// lifecycle.Create call inside TestSyncCreatedOrgContact failed with -// "CouchDB(not_found): missing" because instance.Service.Get queried -// _design/domain-and-aliases on the global instances DB and that design -// doc had never been created. -// -// TODO: the more robust fix is to make instance.Service.Get treat any -// CouchDB "not_found" as ErrNotFound (it currently only handles -// no_db_file / "Database does not exist."). That would remove the implicit -// dependency for every package, not just this one. The repercussions on -// other Get callers haven't been fully audited though, so this localized -// bootstrap stays in place until that broader change is vetted. func TestMain(m *testing.M) { - if err := loadTestConfigForMain(); err != nil { - log.Fatalf("rabbitmq tests: could not load test config: %s", err) - } - // Best-effort: if CouchDB is unreachable, individual tests that need it - // will fail via testutils.NeedCouchdb(t). We don't want TestMain itself - // to hard-fail in environments where CouchDB is intentionally absent. - if _, err := couchdb.CheckStatus(context.Background()); err == nil { - if err := couchdb.InitGlobalDB(context.Background()); err != nil { - log.Printf("rabbitmq tests: could not init global CouchDB: %s", err) - } - } - os.Exit(m.Run()) -} - -// loadTestConfigForMain mirrors config.UseTestFile but without the -// *testing.T plumbing so it can be called from TestMain. -func loadTestConfigForMain() error { - v := viper.New() - v.SetConfigName("cozy.test") - v.AddConfigPath("$HOME/.cozy") - v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - v.SetEnvPrefix("cozy") - v.AutomaticEnv() - v.SetDefault("host", "localhost") - v.SetDefault("port", 8080) - v.SetDefault("assets", "./assets") - v.SetDefault("subdomains", "nested") - v.SetDefault("fs.url", "mem://test") - v.SetDefault("couchdb.url", "http://localhost:5984/") - v.SetDefault("log.level", "info") - if err := v.ReadInConfig(); err != nil { - if _, isMissing := err.(viper.ConfigFileNotFoundError); !isMissing { - return err - } - } - return config.UseViper(v) + os.Exit(testutils.RunTestMainWithCouchDB(m)) } diff --git a/tests/testutils/test_utils.go b/tests/testutils/test_utils.go index 90c8f6f2c85..d48f1b63e81 100644 --- a/tests/testutils/test_utils.go +++ b/tests/testutils/test_utils.go @@ -8,6 +8,7 @@ import ( "flag" "fmt" "io" + "log" "net/http/httptest" "net/url" "path" @@ -87,6 +88,24 @@ func NeedCouchdb(t *testing.T) { } } +// RunTestMainWithCouchDB loads the standard test configuration and initializes +// global CouchDB views when CouchDB is available. +func RunTestMainWithCouchDB(m *testing.M) int { + if err := config.LoadTestFile(); err != nil { + log.Printf("test setup: could not load test config: %s", err) + return 1 + } + + ctx := context.Background() + if _, err := couchdb.CheckStatus(ctx); err == nil { + if err := couchdb.InitGlobalDB(ctx); err != nil { + log.Printf("test setup: could not initialize global CouchDB: %s", err) + } + } + + return m.Run() +} + // TODO can be used as a reminder to do something in the future. The test that // calls TODO will fail after the limit date, which is an efficient way to not // forget about it.