[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 <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
This commit is contained in:
tobi 2025-07-09 17:25:45 +02:00 committed by tobi
commit 352353ce7a
8 changed files with 126 additions and 6 deletions

View file

@ -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.

View file

@ -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, "")

View file

@ -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

View file

@ -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 {

View file

@ -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)
}

View file

@ -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()

View file

@ -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",

View file

@ -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),
}
}