From 352353ce7a33c3ac26fbecd597ab24ae2f9c9864 Mon Sep 17 00:00:00 2001 From: tobi Date: Wed, 9 Jul 2025 17:25:45 +0200 Subject: [PATCH] [chore/testing] Add env vars to skip testrig setup/teardown (#4317) Add flags to skip local testrig db setup and teardown, to allow somewhat easier testing of migrations. Documents env vars available to the testrig. Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4317 Co-authored-by: tobi Co-committed-by: tobi --- CONTRIBUTING.md | 31 ++++++++++- cmd/gotosocial/action/testrig/testrig.go | 12 ++++- cmd/gotosocial/testrig.go | 2 + internal/config/config.go | 2 + internal/config/flags.go | 11 ++++ internal/config/helpers.gen.go | 66 +++++++++++++++++++++++- test/envparsing.sh | 2 + testrig/config.go | 6 ++- 8 files changed, 126 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 790e5a949..1a5d94c24 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,6 +25,7 @@ These contribution guidelines were adapted from / inspired by those of Gitea (ht - [Style / Linting / Formatting](#style-linting-formatting) - [Testing](#testing) - [Standalone Testrig with Pinafore](#standalone-testrig-with-pinafore) + - [Configuring the Standalone Testrig](#configuring-the-standalone-testrig) - [Running automated tests](#running-automated-tests) - [SQLite](#sqlite) - [Postgres](#postgres) @@ -422,10 +423,36 @@ At the login screen, enter the email address `zork@example.org` and password `pa Note the following constraints: -- Since the testrig uses an in-memory database, the database will be destroyed when the testrig is stopped. -- If you stop the testrig and start it again, any tokens or applications you created during your tests will also be removed. As such, you need to log out and in again every time you stop/start the rig. +- Since the testrig uses an in-memory database by default, the database will be destroyed when the testrig is stopped. +- If you stop the testrig and start it again, by default any tokens or applications you created during your tests will also be removed. As such, you need to log out and in again every time you stop/start the rig. - The testrig does not make any actual external HTTP calls, so federation will not work from a testrig. +##### Configuring the Standalone Testrig + +By default the standalone testrig uses an in-memory SQLite database, which is filled with test data when starting up, and is cleared when shutting down, but you can tweak this (and a few other settings) with environment variables: + +- `GTS_LOG_LEVEL` - you can set this to `trace` if you want to see all DB queries. +- `GTS_TESTRIG_SKIP_DB_SETUP` - set this to any value to skip the creation of tables and population of test data when the testrig starts. +- `GTS_TESTRIG_SKIP_DB_TEARDOWN` - set this to any value to skip the deletion of tables and test data when the testrig stops. +- `GTS_STORAGE_BACKEND` - this uses in-memory storage by default, but you can set this to `s3` to use a locally-running Minio etc for testing. +- `GTS_DB_TYPE` - you can change this to `postgres` to test against a locally-running Postgres intance. +- `GTS_DB_ADDRESS` - this is set to `:memory:` by default. You can change this to use an sqlite.db file somewhere, or set it to a Postgres address. +- `GTS_DB_PORT`, `GTS_DB_USER`, `GTS_DB_PASSWORD`, `GTS_DB_DATABASE`, `GTS_DB_TLS_MODE`, `GTS_DB_TLS_CA_CERT` - you can set these if you change `GTS_DB_ADDRESS` to `postgres` and don't use `GTS_DB_POSTGRES_CONNECTION_STRING`. +- `GTS_DB_POSTGRES_CONNECTION_STRING` - use this to provide a Postgres connection string if you don't want to set all the db env variables mentioned in the previous point. +- `GTS_ADVANCED_SCRAPER_DETERRENCE_ENABLED`, `GTS_ADVANCED_SCRAPER_DETERRENCE_DIFFICULTY` - set these if you want to try out the PoW scraper deterrence locally. + +Using these variables you can also (albeit awkwardly) test migrations from one schema to another. + +For example, to test SQLite migrations: + +1. Switch to main branch. +2. Build the debug binary, and then start the testrig with `DEBUG=1 GTS_LOG_LEVEL=trace GTS_DB_ADDRESS=./sqlite.test.db GTS_TESTRIG_SKIP_DB_TEARDOWN=1 ./gotosocial testrig start`. This instructs the testrig to use trace logging, use an actual file for the SQLite db, and to skip tearing it down when finished. +3. Stop the testrig. +4. The file `sqlite.test.db` now contains the schema and test models from the main branch. +5. Switch to the branch with the migration you want to test. +6. Build the debug binary, and then start the testrig with `DEBUG=1 GTS_LOG_LEVEL=trace GTS_DB_ADDRESS=./sqlite.test.db GTS_TESTRIG_SKIP_DB_SETUP=1 ./gotosocial testrig start`. This instructs the testrig to use trace logging, and to use the already-populated sqlite.test.db file. +7. You should see logging for migrations. + #### Running automated tests Tests can be run against both SQLite and Postgres. diff --git a/cmd/gotosocial/action/testrig/testrig.go b/cmd/gotosocial/action/testrig/testrig.go index 6010d4047..2b70a3447 100644 --- a/cmd/gotosocial/action/testrig/testrig.go +++ b/cmd/gotosocial/action/testrig/testrig.go @@ -92,9 +92,14 @@ var Start action.GTSAction = func(ctx context.Context) error { } if state.DB != nil { + // Clean up database by + // dropping tables if required. + if !config.GetTestrigSkipDBTeardown() { + testrig.StandardDBTeardown(state.DB) + } + // Lastly, if database service was started, // ensure it gets closed now all else stopped. - testrig.StandardDBTeardown(state.DB) if err := state.DB.Close(); err != nil { log.Errorf(ctx, "error stopping database: %v", err) } @@ -125,7 +130,10 @@ var Start action.GTSAction = func(ctx context.Context) error { // that twice, we can just start the initialized caches. state.Caches.Start() - testrig.StandardDBSetup(state.DB, nil) + // Populate database tables + data if required. + if !config.GetTestrigSkipDBSetup() { + testrig.StandardDBSetup(state.DB, nil) + } // Get the instance account (we'll need this later). instanceAccount, err := state.DB.GetInstanceAccount(ctx, "") diff --git a/cmd/gotosocial/testrig.go b/cmd/gotosocial/testrig.go index ee21b7312..55498243b 100644 --- a/cmd/gotosocial/testrig.go +++ b/cmd/gotosocial/testrig.go @@ -19,6 +19,7 @@ package main import ( "code.superseriousbusiness.org/gotosocial/cmd/gotosocial/action/testrig" + "code.superseriousbusiness.org/gotosocial/internal/config" "github.com/spf13/cobra" ) @@ -38,6 +39,7 @@ func testrigCommands() *cobra.Command { } testrigCmd.AddCommand(testrigStartCmd) + config.AddTestrig(testrigCmd) return testrigCmd } return nil diff --git a/internal/config/config.go b/internal/config/config.go index 8139770e0..95d74342e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -182,6 +182,8 @@ type Configuration struct { AdminMediaPruneDryRun bool `name:"dry-run" usage:"perform a dry run and only log number of items eligible for pruning" ephemeral:"yes"` AdminMediaListLocalOnly bool `name:"local-only" usage:"list only local attachments/emojis; if specified then remote-only cannot also be true" ephemeral:"yes"` AdminMediaListRemoteOnly bool `name:"remote-only" usage:"list only remote attachments/emojis; if specified then local-only cannot also be true" ephemeral:"yes"` + TestrigSkipDBSetup bool `name:"skip-db-setup" usage:"skip testrig database setup with population of test models" ephemeral:"yes"` + TestrigSkipDBTeardown bool `name:"skip-db-teardown" usage:"skip testrig database teardown (i.e. data deletion and tables dropped)" ephemeral:"yes"` } type HTTPClientConfiguration struct { diff --git a/internal/config/flags.go b/internal/config/flags.go index 3c94b5799..eb862fc9a 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -84,3 +84,14 @@ func AddAdminMediaPrune(cmd *cobra.Command) { usage := fieldtag("AdminMediaPruneDryRun", "usage") cmd.Flags().Bool(name, true, usage) } + +// AddTestrig attaches flags pertaining to testrig commands. +func AddTestrig(cmd *cobra.Command) { + skipDBSetup := TestrigSkipDBSetupFlag + skipDBSetupUsage := fieldtag("TestrigSkipDBSetup", "usage") + cmd.Flags().Bool(skipDBSetup, false, skipDBSetupUsage) + + skipDBTeardown := TestrigSkipDBTeardownFlag + skipDBTeardownUsage := fieldtag("TestrigSkipDBTeardown", "usage") + cmd.Flags().Bool(skipDBTeardown, false, skipDBTeardownUsage) +} diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go index 36dd927f8..4aea742c1 100644 --- a/internal/config/helpers.gen.go +++ b/internal/config/helpers.gen.go @@ -220,6 +220,8 @@ const ( AdminMediaPruneDryRunFlag = "dry-run" AdminMediaListLocalOnlyFlag = "local-only" AdminMediaListRemoteOnlyFlag = "remote-only" + TestrigSkipDBSetupFlag = "skip-db-setup" + TestrigSkipDBTeardownFlag = "skip-db-teardown" ) func (cfg *Configuration) RegisterFlags(flags *pflag.FlagSet) { @@ -410,7 +412,7 @@ func (cfg *Configuration) RegisterFlags(flags *pflag.FlagSet) { } func (cfg *Configuration) MarshalMap() map[string]any { - cfgmap := make(map[string]any, 191) + cfgmap := make(map[string]any, 193) cfgmap["log-level"] = cfg.LogLevel cfgmap["log-timestamp-format"] = cfg.LogTimestampFormat cfgmap["log-db-queries"] = cfg.LogDbQueries @@ -602,6 +604,8 @@ func (cfg *Configuration) MarshalMap() map[string]any { cfgmap["dry-run"] = cfg.AdminMediaPruneDryRun cfgmap["local-only"] = cfg.AdminMediaListLocalOnly cfgmap["remote-only"] = cfg.AdminMediaListRemoteOnly + cfgmap["skip-db-setup"] = cfg.TestrigSkipDBSetup + cfgmap["skip-db-teardown"] = cfg.TestrigSkipDBTeardown return cfgmap } @@ -2173,6 +2177,22 @@ func (cfg *Configuration) UnmarshalMap(cfgmap map[string]any) error { } } + if ival, ok := cfgmap["skip-db-setup"]; ok { + var err error + cfg.TestrigSkipDBSetup, err = cast.ToBoolE(ival) + if err != nil { + return fmt.Errorf("error casting %#v -> bool for 'skip-db-setup': %w", ival, err) + } + } + + if ival, ok := cfgmap["skip-db-teardown"]; ok { + var err error + cfg.TestrigSkipDBTeardown, err = cast.ToBoolE(ival) + if err != nil { + return fmt.Errorf("error casting %#v -> bool for 'skip-db-teardown': %w", ival, err) + } + } + return nil } @@ -6406,6 +6426,50 @@ func GetAdminMediaListRemoteOnly() bool { return global.GetAdminMediaListRemoteO // SetAdminMediaListRemoteOnly safely sets the value for global configuration 'AdminMediaListRemoteOnly' field func SetAdminMediaListRemoteOnly(v bool) { global.SetAdminMediaListRemoteOnly(v) } +// GetTestrigSkipDBSetup safely fetches the Configuration value for state's 'TestrigSkipDBSetup' field +func (st *ConfigState) GetTestrigSkipDBSetup() (v bool) { + st.mutex.RLock() + v = st.config.TestrigSkipDBSetup + st.mutex.RUnlock() + return +} + +// SetTestrigSkipDBSetup safely sets the Configuration value for state's 'TestrigSkipDBSetup' field +func (st *ConfigState) SetTestrigSkipDBSetup(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.TestrigSkipDBSetup = v + st.reloadToViper() +} + +// GetTestrigSkipDBSetup safely fetches the value for global configuration 'TestrigSkipDBSetup' field +func GetTestrigSkipDBSetup() bool { return global.GetTestrigSkipDBSetup() } + +// SetTestrigSkipDBSetup safely sets the value for global configuration 'TestrigSkipDBSetup' field +func SetTestrigSkipDBSetup(v bool) { global.SetTestrigSkipDBSetup(v) } + +// GetTestrigSkipDBTeardown safely fetches the Configuration value for state's 'TestrigSkipDBTeardown' field +func (st *ConfigState) GetTestrigSkipDBTeardown() (v bool) { + st.mutex.RLock() + v = st.config.TestrigSkipDBTeardown + st.mutex.RUnlock() + return +} + +// SetTestrigSkipDBTeardown safely sets the Configuration value for state's 'TestrigSkipDBTeardown' field +func (st *ConfigState) SetTestrigSkipDBTeardown(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.TestrigSkipDBTeardown = v + st.reloadToViper() +} + +// GetTestrigSkipDBTeardown safely fetches the value for global configuration 'TestrigSkipDBTeardown' field +func GetTestrigSkipDBTeardown() bool { return global.GetTestrigSkipDBTeardown() } + +// SetTestrigSkipDBTeardown safely sets the value for global configuration 'TestrigSkipDBTeardown' field +func SetTestrigSkipDBTeardown(v bool) { global.SetTestrigSkipDBTeardown(v) } + // GetTotalOfMemRatios safely fetches the combined value for all the state's mem ratio fields func (st *ConfigState) GetTotalOfMemRatios() (total float64) { st.mutex.RLock() diff --git a/test/envparsing.sh b/test/envparsing.sh index a6247ece5..4dba8f155 100755 --- a/test/envparsing.sh +++ b/test/envparsing.sh @@ -176,6 +176,8 @@ EXPECT=$(cat << "EOF" "protocol": "http", "remote-only": false, "request-id-header": "X-Trace-Id", + "skip-db-setup": false, + "skip-db-teardown": false, "smtp-disclose-recipients": true, "smtp-from": "queen.rip.in.piss@terfisland.org", "smtp-host": "example.com", diff --git a/testrig/config.go b/testrig/config.go index 8d1c6581d..fdc026e61 100644 --- a/testrig/config.go +++ b/testrig/config.go @@ -128,7 +128,7 @@ func testDefaults() config.Configuration { ThumbMaxPixels: 512, }, - // the testrig only uses in-memory storage, so we can + // the testrig uses in-memory storage by default, so we can // safely set this value to 'test' to avoid running storage // migrations, and other silly things like that StorageBackend: "test", @@ -191,6 +191,10 @@ func testDefaults() config.Configuration { // simply use cache defaults. Cache: config.Defaults.Cache, + + // Testrig-specific flags. + TestrigSkipDBSetup: envBool("GTS_TESTRIG_SKIP_DB_SETUP", false), + TestrigSkipDBTeardown: envBool("GTS_TESTRIG_SKIP_DB_TEARDOWN", false), } }