901 lines
34 KiB
Go
901 lines
34 KiB
Go
package cosmovisor
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/pelletier/go-toml/v2"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"cosmossdk.io/x/upgrade/plan"
|
|
)
|
|
|
|
type argsTestSuite struct {
|
|
suite.Suite
|
|
}
|
|
|
|
func TestArgsTestSuite(t *testing.T) {
|
|
suite.Run(t, new(argsTestSuite))
|
|
}
|
|
|
|
// cosmovisorEnv are the string values of environment variables used to configure Cosmovisor.
|
|
type cosmovisorEnv struct {
|
|
Home string
|
|
Name string
|
|
DownloadBin string
|
|
DownloadMustHaveChecksum string
|
|
RestartUpgrade string
|
|
RestartDelay string
|
|
SkipBackup string
|
|
DataBackupPath string
|
|
Interval string
|
|
PreupgradeMaxRetries string
|
|
DisableLogs string
|
|
ColorLogs string
|
|
TimeFormatLogs string
|
|
CustomPreupgrade string
|
|
DisableRecase string
|
|
ShutdownGrace string
|
|
}
|
|
|
|
type envMap struct {
|
|
val string
|
|
allowEmpty bool
|
|
}
|
|
|
|
// ToMap creates a map of the cosmovisorEnv where the keys are the env var names.
|
|
func (c cosmovisorEnv) ToMap() map[string]envMap {
|
|
return map[string]envMap{
|
|
EnvHome: {val: c.Home, allowEmpty: false},
|
|
EnvName: {val: c.Name, allowEmpty: false},
|
|
EnvDownloadBin: {val: c.DownloadBin, allowEmpty: false},
|
|
EnvDownloadMustHaveChecksum: {val: c.DownloadMustHaveChecksum, allowEmpty: false},
|
|
EnvRestartUpgrade: {val: c.RestartUpgrade, allowEmpty: false},
|
|
EnvRestartDelay: {val: c.RestartDelay, allowEmpty: false},
|
|
EnvShutdownGrace: {val: c.ShutdownGrace, allowEmpty: false},
|
|
EnvSkipBackup: {val: c.SkipBackup, allowEmpty: false},
|
|
EnvDataBackupPath: {val: c.DataBackupPath, allowEmpty: false},
|
|
EnvInterval: {val: c.Interval, allowEmpty: false},
|
|
EnvPreupgradeMaxRetries: {val: c.PreupgradeMaxRetries, allowEmpty: false},
|
|
EnvDisableLogs: {val: c.DisableLogs, allowEmpty: false},
|
|
EnvColorLogs: {val: c.ColorLogs, allowEmpty: false},
|
|
EnvTimeFormatLogs: {val: c.TimeFormatLogs, allowEmpty: true},
|
|
EnvCustomPreupgrade: {val: c.CustomPreupgrade, allowEmpty: true},
|
|
EnvDisableRecase: {val: c.DisableRecase, allowEmpty: true},
|
|
}
|
|
}
|
|
|
|
// Set sets the field in this cosmovisorEnv corresponding to the provided envVar to the given envVal.
|
|
func (c *cosmovisorEnv) Set(envVar, envVal string) {
|
|
switch envVar {
|
|
case EnvHome:
|
|
c.Home = envVal
|
|
case EnvName:
|
|
c.Name = envVal
|
|
case EnvDownloadBin:
|
|
c.DownloadBin = envVal
|
|
case EnvDownloadMustHaveChecksum:
|
|
c.DownloadMustHaveChecksum = envVal
|
|
case EnvRestartUpgrade:
|
|
c.RestartUpgrade = envVal
|
|
case EnvRestartDelay:
|
|
c.RestartDelay = envVal
|
|
case EnvShutdownGrace:
|
|
c.ShutdownGrace = envVal
|
|
case EnvSkipBackup:
|
|
c.SkipBackup = envVal
|
|
case EnvDataBackupPath:
|
|
c.DataBackupPath = envVal
|
|
case EnvInterval:
|
|
c.Interval = envVal
|
|
case EnvPreupgradeMaxRetries:
|
|
c.PreupgradeMaxRetries = envVal
|
|
case EnvDisableLogs:
|
|
c.DisableLogs = envVal
|
|
case EnvColorLogs:
|
|
c.ColorLogs = envVal
|
|
case EnvTimeFormatLogs:
|
|
c.TimeFormatLogs = envVal
|
|
case EnvCustomPreupgrade:
|
|
c.CustomPreupgrade = envVal
|
|
case EnvDisableRecase:
|
|
c.DisableRecase = envVal
|
|
default:
|
|
panic(fmt.Errorf("Unknown environment variable [%s]. Cannot set field to [%s]. ", envVar, envVal))
|
|
}
|
|
}
|
|
|
|
// clearEnv clears environment variables and what they were.
|
|
// Designed to be used like this:
|
|
//
|
|
// initialEnv := clearEnv()
|
|
// defer setEnv(nil, initialEnv)
|
|
func (s *argsTestSuite) clearEnv() *cosmovisorEnv {
|
|
s.T().Logf("Clearing environment variables.")
|
|
rv := cosmovisorEnv{}
|
|
for envVar := range rv.ToMap() {
|
|
rv.Set(envVar, os.Getenv(envVar))
|
|
s.Require().NoError(os.Unsetenv(envVar))
|
|
}
|
|
return &rv
|
|
}
|
|
|
|
// setEnv sets environment variables to the values provided.
|
|
// If t is not nil, and there's a problem, the test will fail immediately.
|
|
// If t is nil, problems will just be logged using s.T().
|
|
func (s *argsTestSuite) setEnv(t *testing.T, env *cosmovisorEnv) { //nolint:thelper // false positive
|
|
if t == nil {
|
|
s.T().Logf("Restoring environment variables.")
|
|
}
|
|
for envVar, envVal := range env.ToMap() {
|
|
var err error
|
|
var msg string
|
|
if len(envVal.val) != 0 || envVal.allowEmpty {
|
|
err = os.Setenv(envVar, envVal.val)
|
|
msg = fmt.Sprintf("setting %s to %s", envVar, envVal.val)
|
|
} else {
|
|
err = os.Unsetenv(envVar)
|
|
msg = fmt.Sprintf("unsetting %s", envVar)
|
|
}
|
|
switch {
|
|
case t != nil:
|
|
require.NoError(t, err, msg)
|
|
case err != nil:
|
|
s.T().Logf("error %s: %v", msg, err)
|
|
default:
|
|
s.T().Logf("done %s", msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *argsTestSuite) TestConfigPaths() {
|
|
cases := map[string]struct {
|
|
cfg Config
|
|
upgradeName string
|
|
expectRoot string
|
|
expectGenesis string
|
|
expectUpgrade string
|
|
}{
|
|
"simple": {
|
|
cfg: Config{Home: "/foo", Name: "myd"},
|
|
upgradeName: "bar",
|
|
expectRoot: fmt.Sprintf("/foo/%s", rootName),
|
|
expectGenesis: fmt.Sprintf("/foo/%s/genesis/bin/myd", rootName),
|
|
expectUpgrade: fmt.Sprintf("/foo/%s/upgrades/bar/bin/myd", rootName),
|
|
},
|
|
"handle space": {
|
|
cfg: Config{Home: "/longer/prefix/", Name: "yourd"},
|
|
upgradeName: "some spaces",
|
|
expectRoot: fmt.Sprintf("/longer/prefix/%s", rootName),
|
|
expectGenesis: fmt.Sprintf("/longer/prefix/%s/genesis/bin/yourd", rootName),
|
|
expectUpgrade: "/longer/prefix/cosmovisor/upgrades/some%20spaces/bin/yourd",
|
|
},
|
|
"handle casing": {
|
|
cfg: Config{Home: "/longer/prefix/", Name: "appd"},
|
|
upgradeName: "myUpgrade",
|
|
expectRoot: fmt.Sprintf("/longer/prefix/%s", rootName),
|
|
expectGenesis: fmt.Sprintf("/longer/prefix/%s/genesis/bin/appd", rootName),
|
|
expectUpgrade: "/longer/prefix/cosmovisor/upgrades/myUpgrade/bin/appd",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
s.Require().Equal(tc.cfg.Root(), filepath.FromSlash(tc.expectRoot))
|
|
s.Require().Equal(tc.cfg.GenesisBin(), filepath.FromSlash(tc.expectGenesis))
|
|
s.Require().Equal(tc.cfg.UpgradeBin(tc.upgradeName), filepath.FromSlash(tc.expectUpgrade))
|
|
}
|
|
}
|
|
|
|
// Test validate
|
|
// add more test in test validate
|
|
func (s *argsTestSuite) TestValidate() {
|
|
relPath := filepath.Join("testdata", "validate")
|
|
absPath, err := filepath.Abs(relPath)
|
|
s.Require().NoError(err)
|
|
|
|
testdata, err := filepath.Abs("testdata")
|
|
s.Require().NoError(err)
|
|
|
|
cases := map[string]struct {
|
|
cfg Config
|
|
valid bool
|
|
}{
|
|
"happy": {
|
|
cfg: Config{Home: absPath, Name: "bind", DataBackupPath: absPath},
|
|
valid: true,
|
|
},
|
|
"happy with download": {
|
|
cfg: Config{Home: absPath, Name: "bind", AllowDownloadBinaries: true, DataBackupPath: absPath},
|
|
valid: true,
|
|
},
|
|
"happy with skip data backup": {
|
|
cfg: Config{Home: absPath, Name: "bind", UnsafeSkipBackup: true, DataBackupPath: absPath},
|
|
valid: true,
|
|
},
|
|
"happy with skip data backup and empty data backup path": {
|
|
cfg: Config{Home: absPath, Name: "bind", UnsafeSkipBackup: true, DataBackupPath: ""},
|
|
valid: true,
|
|
},
|
|
"happy with skip data backup and no such data backup path dir": {
|
|
cfg: Config{Home: absPath, Name: "bind", UnsafeSkipBackup: true, DataBackupPath: filepath.FromSlash("/no/such/dir")},
|
|
valid: true,
|
|
},
|
|
"happy with skip data backup and relative data backup path": {
|
|
cfg: Config{Home: absPath, Name: "bind", UnsafeSkipBackup: true, DataBackupPath: relPath},
|
|
valid: true,
|
|
},
|
|
"missing home": {
|
|
cfg: Config{Name: "bind"},
|
|
valid: false,
|
|
},
|
|
"missing name": {
|
|
cfg: Config{Home: absPath},
|
|
valid: false,
|
|
},
|
|
"relative home path": {
|
|
cfg: Config{Home: relPath, Name: "bind"},
|
|
valid: false,
|
|
},
|
|
"no upgrade manager subdir": {
|
|
cfg: Config{Home: testdata, Name: "bind"},
|
|
valid: false,
|
|
},
|
|
"no such home dir": {
|
|
cfg: Config{Home: filepath.FromSlash("/no/such/dir"), Name: "bind"},
|
|
valid: false,
|
|
},
|
|
"empty data backup path": {
|
|
cfg: Config{Home: absPath, Name: "bind", DataBackupPath: ""},
|
|
valid: false,
|
|
},
|
|
"no such data backup path dir": {
|
|
cfg: Config{Home: absPath, Name: "bind", DataBackupPath: filepath.FromSlash("/no/such/dir")},
|
|
valid: false,
|
|
},
|
|
"relative data backup path": {
|
|
cfg: Config{Home: absPath, Name: "bind", DataBackupPath: relPath},
|
|
valid: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
errs := tc.cfg.validate()
|
|
if tc.valid {
|
|
s.Require().Len(errs, 0)
|
|
} else {
|
|
s.Require().Greater(len(errs), 0, "number of errors returned")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *argsTestSuite) TestEnsureBin() {
|
|
relPath := filepath.Join("testdata", "validate")
|
|
absPath, err := filepath.Abs(relPath)
|
|
s.Require().NoError(err)
|
|
|
|
cfg := Config{Home: absPath, Name: "dummyd", DataBackupPath: absPath}
|
|
s.Require().Len(cfg.validate(), 0, "validation errors")
|
|
|
|
s.Require().NoError(plan.EnsureBinary(cfg.GenesisBin()))
|
|
|
|
cases := map[string]struct {
|
|
upgrade string
|
|
hasBin bool
|
|
}{
|
|
"proper": {"chain2", true},
|
|
"no binary": {"nobin", false},
|
|
"no directory": {"foobarbaz", false},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
err := plan.EnsureBinary(cfg.UpgradeBin(tc.upgrade))
|
|
if tc.hasBin {
|
|
s.Require().NoError(err)
|
|
} else {
|
|
s.Require().Error(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *argsTestSuite) TestBooleanOption() {
|
|
initialEnv := s.clearEnv()
|
|
defer s.setEnv(nil, initialEnv)
|
|
|
|
name := "COSMOVISOR_TEST_VAL"
|
|
|
|
check := func(def, expected, isErr bool, msg string) {
|
|
v, err := BooleanOption(name, def)
|
|
if isErr {
|
|
s.Require().Error(err)
|
|
return
|
|
}
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(expected, v, msg)
|
|
}
|
|
|
|
os.Setenv(name, "")
|
|
check(true, true, false, "should correctly set default value")
|
|
check(false, false, false, "should correctly set default value")
|
|
|
|
os.Setenv(name, "wrong")
|
|
check(true, true, true, "should error on wrong value")
|
|
os.Setenv(name, "truee")
|
|
check(true, true, true, "should error on wrong value")
|
|
|
|
os.Setenv(name, "false")
|
|
check(true, false, false, "should handle false value")
|
|
check(false, false, false, "should handle false value")
|
|
os.Setenv(name, "faLSe")
|
|
check(true, false, false, "should handle false value case not sensitive")
|
|
check(false, false, false, "should handle false value case not sensitive")
|
|
|
|
os.Setenv(name, "true")
|
|
check(true, true, false, "should handle true value")
|
|
check(false, true, false, "should handle true value")
|
|
|
|
os.Setenv(name, "TRUE")
|
|
check(true, true, false, "should handle true value case not sensitive")
|
|
check(false, true, false, "should handle true value case not sensitive")
|
|
}
|
|
|
|
func (s *argsTestSuite) TestTimeFormat() {
|
|
initialEnv := s.clearEnv()
|
|
defer s.setEnv(nil, initialEnv)
|
|
|
|
name := "COSMOVISOR_TEST_VAL"
|
|
|
|
check := func(def, expected string, isErr bool, msg string) {
|
|
v, err := TimeFormatOptionFromEnv(name, def)
|
|
if isErr {
|
|
s.Require().Error(err)
|
|
return
|
|
}
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(expected, v, msg)
|
|
}
|
|
|
|
os.Unsetenv(name)
|
|
check(time.Kitchen, time.Kitchen, false, "should correctly set default value")
|
|
|
|
os.Setenv(name, "")
|
|
check(time.Kitchen, "", false, "should correctly set to a none")
|
|
|
|
os.Setenv(name, "wrong")
|
|
check(time.Kitchen, "", true, "should error on wrong value")
|
|
|
|
os.Setenv(name, "layout")
|
|
check(time.Kitchen, time.Layout, false, "should handle layout value")
|
|
os.Setenv(name, "ansic")
|
|
check(time.Kitchen, time.ANSIC, false, "should handle ansic value")
|
|
os.Setenv(name, "unixdate")
|
|
check(time.Kitchen, time.UnixDate, false, "should handle unixdate value")
|
|
os.Setenv(name, "rubydate")
|
|
check(time.Kitchen, time.RubyDate, false, "should handle rubydate value")
|
|
os.Setenv(name, "rfc822")
|
|
check(time.Kitchen, time.RFC822, false, "should handle rfc822 value")
|
|
os.Setenv(name, "rfc822z")
|
|
check(time.Kitchen, time.RFC822Z, false, "should handle rfc822z value")
|
|
os.Setenv(name, "rfc850")
|
|
check(time.Kitchen, time.RFC850, false, "should handle rfc850 value")
|
|
os.Setenv(name, "rfc1123")
|
|
check(time.Kitchen, time.RFC1123, false, "should handle rfc1123 value")
|
|
os.Setenv(name, "rfc1123z")
|
|
check(time.Kitchen, time.RFC1123Z, false, "should handle rfc1123z value")
|
|
os.Setenv(name, "rfc3339")
|
|
check(time.Kitchen, time.RFC3339, false, "should handle rfc3339 value")
|
|
os.Setenv(name, "rfc3339nano")
|
|
check(time.Kitchen, time.RFC3339Nano, false, "should handle rfc3339nano value")
|
|
os.Setenv(name, "kitchen")
|
|
check(time.Kitchen, time.Kitchen, false, "should handle kitchen value")
|
|
}
|
|
|
|
func (s *argsTestSuite) TestDetailString() {
|
|
home := "/home"
|
|
name := "test-name"
|
|
allowDownloadBinaries := true
|
|
downloadMustHaveChecksum := true
|
|
restartAfterUpgrade := true
|
|
pollInterval := 406 * time.Millisecond
|
|
unsafeSkipBackup := false
|
|
dataBackupPath := "/home"
|
|
preupgradeMaxRetries := 8
|
|
cfg := &Config{
|
|
Home: home,
|
|
Name: name,
|
|
AllowDownloadBinaries: allowDownloadBinaries,
|
|
DownloadMustHaveChecksum: downloadMustHaveChecksum,
|
|
RestartAfterUpgrade: restartAfterUpgrade,
|
|
PollInterval: pollInterval,
|
|
UnsafeSkipBackup: unsafeSkipBackup,
|
|
DataBackupPath: dataBackupPath,
|
|
PreUpgradeMaxRetries: preupgradeMaxRetries,
|
|
}
|
|
|
|
expectedPieces := []string{
|
|
"Configurable Values:",
|
|
fmt.Sprintf("%s: %s", EnvHome, home),
|
|
fmt.Sprintf("%s: %s", EnvName, name),
|
|
fmt.Sprintf("%s: %t", EnvDownloadBin, allowDownloadBinaries),
|
|
fmt.Sprintf("%s: %t", EnvDownloadMustHaveChecksum, downloadMustHaveChecksum),
|
|
fmt.Sprintf("%s: %t", EnvRestartUpgrade, restartAfterUpgrade),
|
|
fmt.Sprintf("%s: %s", EnvInterval, pollInterval),
|
|
fmt.Sprintf("%s: %t", EnvSkipBackup, unsafeSkipBackup),
|
|
fmt.Sprintf("%s: %s", EnvDataBackupPath, home),
|
|
fmt.Sprintf("%s: %d", EnvPreupgradeMaxRetries, preupgradeMaxRetries),
|
|
fmt.Sprintf("%s: %t", EnvDisableLogs, cfg.DisableLogs),
|
|
fmt.Sprintf("%s: %t", EnvColorLogs, cfg.ColorLogs),
|
|
fmt.Sprintf("%s: %s", EnvTimeFormatLogs, cfg.TimeFormatLogs),
|
|
"Derived Values:",
|
|
fmt.Sprintf("Root Dir: %s", home),
|
|
fmt.Sprintf("Upgrade Dir: %s", home),
|
|
fmt.Sprintf("Genesis Bin: %s", home),
|
|
fmt.Sprintf("Monitored File: %s", home),
|
|
fmt.Sprintf("Data Backup Dir: %s", home),
|
|
}
|
|
|
|
actual := cfg.DetailString()
|
|
|
|
for _, piece := range expectedPieces {
|
|
s.Assert().Contains(actual, piece)
|
|
}
|
|
}
|
|
|
|
var newConfig = func(
|
|
home, name string,
|
|
downloadBin bool,
|
|
downloadMustHaveChecksum bool,
|
|
restartUpgrade bool,
|
|
restartDelay int,
|
|
skipBackup bool,
|
|
dataBackupPath string,
|
|
interval, preupgradeMaxRetries int,
|
|
grpcAddress string,
|
|
disableLogs, colorLogs bool,
|
|
timeFormatLogs string,
|
|
customPreUpgrade string,
|
|
disableRecase bool,
|
|
shutdownGrace int,
|
|
) *Config {
|
|
return &Config{
|
|
Home: home,
|
|
Name: name,
|
|
AllowDownloadBinaries: downloadBin,
|
|
DownloadMustHaveChecksum: downloadMustHaveChecksum,
|
|
RestartAfterUpgrade: restartUpgrade,
|
|
RestartDelay: time.Millisecond * time.Duration(restartDelay),
|
|
PollInterval: time.Millisecond * time.Duration(interval),
|
|
UnsafeSkipBackup: skipBackup,
|
|
DataBackupPath: dataBackupPath,
|
|
GRPCAddress: grpcAddress,
|
|
PreUpgradeMaxRetries: preupgradeMaxRetries,
|
|
DisableLogs: disableLogs,
|
|
ColorLogs: colorLogs,
|
|
TimeFormatLogs: timeFormatLogs,
|
|
CustomPreUpgrade: customPreUpgrade,
|
|
DisableRecase: disableRecase,
|
|
ShutdownGrace: time.Duration(shutdownGrace),
|
|
}
|
|
}
|
|
|
|
func (s *argsTestSuite) TestGetConfigFromEnv() {
|
|
initialEnv := s.clearEnv()
|
|
defer s.setEnv(nil, initialEnv)
|
|
|
|
relPath := filepath.Join("testdata", "validate")
|
|
absPath, perr := filepath.Abs(relPath)
|
|
s.Require().NoError(perr)
|
|
|
|
tests := []struct {
|
|
name string
|
|
envVals cosmovisorEnv
|
|
expectedCfg *Config
|
|
expectedErrCount int
|
|
}{
|
|
{
|
|
name: "all bad",
|
|
envVals: cosmovisorEnv{
|
|
Home: "",
|
|
Name: "",
|
|
DownloadBin: "bad",
|
|
DownloadMustHaveChecksum: "bad",
|
|
RestartUpgrade: "bad",
|
|
RestartDelay: "bad",
|
|
SkipBackup: "bad",
|
|
DataBackupPath: "bad",
|
|
Interval: "bad",
|
|
PreupgradeMaxRetries: "bad",
|
|
TimeFormatLogs: "bad",
|
|
CustomPreupgrade: "",
|
|
DisableRecase: "bad",
|
|
ShutdownGrace: "bad",
|
|
},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 13,
|
|
},
|
|
{
|
|
name: "all good",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "true", "10s"},
|
|
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", true, 10000000000),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "nothing set",
|
|
envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "", "false", "false", "", "", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 3,
|
|
},
|
|
// Note: Home and Name tests are done in TestValidate
|
|
// timeformat tests are done in the TestTimeFormat
|
|
{
|
|
name: "download bin bad",
|
|
envVals: cosmovisorEnv{absPath, "testname", "bad", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "download bin not set",
|
|
envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "download bin true",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "download bin false",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "download ensure checksum true",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "restart upgrade bad",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "bad", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "restart upgrade not set",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "restart upgrade true",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "restart upgrade true",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "skip unsafe backups bad",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "bad", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "skip unsafe backups not set",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "skip unsafe backups true",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "skip unsafe backups false",
|
|
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "poll interval bad",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "bad", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "poll interval 0",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "0", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "poll interval not set",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "poll interval 600",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "600", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "poll interval 1s",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "1s", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "poll interval -3m",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "-3m", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "restart delay bad",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "bad", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "restart delay 0",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "0", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "restart delay not set",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "restart delay 600",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600", "false", "", "300ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "restart delay 1s",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "1s", "false", "", "303ms", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "restart delay -3m",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "-3m", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "prepupgrade max retries bad",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "bad", "false", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "prepupgrade max retries 0",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "0", "false", "false", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "prepupgrade max retries not set",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false", "false", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "prepupgrade max retries 5",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "false", "false", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "disable logs bad",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "bad", "true", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "disable logs good",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "disable logs color bad",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "true", "bad", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "disable logs color good",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, time.Kitchen, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "disable logs timestamp",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, "", "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "enable rf3339 logs timestamp",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", false, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "invalid logs timestamp format",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "invalid", "preupgrade.sh", "", ""},
|
|
expectedCfg: nil,
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "disable recase good",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "true", ""},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", true, 0),
|
|
expectedErrCount: 0,
|
|
},
|
|
{
|
|
name: "disable recase bad",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "bad", ""},
|
|
expectedErrCount: 1,
|
|
},
|
|
{
|
|
name: "shutdown grace good",
|
|
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "true", "15s"},
|
|
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", true, 15000000000),
|
|
expectedErrCount: 0,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
s.T().Run(tc.name, func(t *testing.T) {
|
|
s.setEnv(t, &tc.envVals)
|
|
cfg, err := GetConfigFromEnv(false)
|
|
if tc.expectedErrCount == 0 {
|
|
assert.NoError(t, err)
|
|
} else if assert.Error(t, err) {
|
|
errCount := 1
|
|
if errMulti, ok := err.(interface{ Unwrap() []error }); ok {
|
|
errCount = len(errMulti.Unwrap())
|
|
}
|
|
assert.Equal(t, tc.expectedErrCount, errCount, "error count")
|
|
}
|
|
assert.Equal(t, tc.expectedCfg, cfg, "config")
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *argsTestSuite) setUpDir() string {
|
|
s.T().Helper()
|
|
|
|
home := s.T().TempDir()
|
|
err := os.MkdirAll(filepath.Join(home, rootName), 0o755)
|
|
s.Require().NoError(err)
|
|
return home
|
|
}
|
|
|
|
func (s *argsTestSuite) setupConfig(home string) string {
|
|
s.T().Helper()
|
|
|
|
cfg := newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, "kitchen", "", true, 10000000000)
|
|
path := filepath.Join(home, rootName, "config.toml")
|
|
f, err := os.Create(path)
|
|
s.Require().NoError(err)
|
|
|
|
enc := toml.NewEncoder(f)
|
|
s.Require().NoError(enc.Encode(&cfg))
|
|
|
|
err = f.Close()
|
|
s.Require().NoError(err)
|
|
|
|
return path
|
|
}
|
|
|
|
func (s *argsTestSuite) TestConfigFromFile() {
|
|
home := s.setUpDir()
|
|
// create a config file
|
|
cfgFilePath := s.setupConfig(home)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
config *Config
|
|
expectedCfg func() *Config
|
|
filePath string
|
|
expectedError string
|
|
malleate func()
|
|
}{
|
|
{
|
|
name: "valid config",
|
|
expectedCfg: func() *Config {
|
|
return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000)
|
|
},
|
|
filePath: cfgFilePath,
|
|
expectedError: "",
|
|
malleate: func() {},
|
|
},
|
|
{
|
|
name: "env variable will override config file fields",
|
|
filePath: cfgFilePath,
|
|
expectedError: "",
|
|
malleate: func() {
|
|
// set env variable different from the config file
|
|
os.Setenv(EnvName, "env-name")
|
|
},
|
|
expectedCfg: func() *Config {
|
|
return newConfig(home, "env-name", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000)
|
|
},
|
|
},
|
|
{
|
|
name: "empty config file path will load config from ENV variables",
|
|
expectedCfg: func() *Config {
|
|
return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000)
|
|
},
|
|
filePath: "",
|
|
expectedError: "",
|
|
malleate: func() {
|
|
s.setEnv(s.T(), &cosmovisorEnv{home, "test", "true", "true", "true", "406ms", "false", home, "8ms", "0", "false", "true", "kitchen", "", "true", "10s"})
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.T().Run(tc.name, func(t *testing.T) {
|
|
tc.malleate()
|
|
actualCfg, err := GetConfigFromFile(tc.filePath)
|
|
if tc.expectedError != "" {
|
|
s.Require().NoError(err)
|
|
s.Require().Contains(err.Error(), tc.expectedError)
|
|
return
|
|
}
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().EqualValues(tc.expectedCfg(), actualCfg)
|
|
})
|
|
}
|
|
}
|
|
|
|
var sink interface{}
|
|
|
|
func BenchmarkDetailString(b *testing.B) {
|
|
cfg := &Config{
|
|
Home: "/foo", Name: "myd",
|
|
AllowDownloadBinaries: true,
|
|
UnsafeSkipBackup: true,
|
|
PollInterval: 450 * time.Second,
|
|
PreUpgradeMaxRetries: 1e7,
|
|
}
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
sink = cfg.DetailString()
|
|
}
|
|
|
|
if sink == nil {
|
|
b.Fatal("Benchmark did not run")
|
|
}
|
|
|
|
// Otherwise reset the sink.
|
|
sink = (interface{})(nil)
|
|
}
|