github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/config/config_test.go (about) 1 package config 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "reflect" 8 "strings" 9 "testing" 10 11 "github.com/go-playground/validator/v10" 12 "github.com/spf13/pflag" 13 "github.com/spf13/viper" 14 "github.com/stretchr/testify/require" 15 "gopkg.in/yaml.v2" 16 17 "github.com/onflow/flow-go/network/netconf" 18 "github.com/onflow/flow-go/utils/unittest" 19 ) 20 21 // TestBindPFlags ensures configuration is bound to the pflag set as expected and configuration values are overridden when set with CLI flags. 22 func TestBindPFlags(t *testing.T) { 23 t.Run("should override config values when any flag is set", func(t *testing.T) { 24 c := defaultConfig(t) 25 flags := testFlagSet(c) 26 err := flags.Set("networking-connection-pruning", "false") 27 require.NoError(t, err) 28 require.NoError(t, flags.Parse(nil)) 29 30 configFileUsed, err := BindPFlags(c, flags) 31 require.NoError(t, err) 32 require.False(t, configFileUsed) 33 require.False(t, c.NetworkConfig.NetworkConnectionPruning) 34 }) 35 t.Run("should return an error if flags are not parsed", func(t *testing.T) { 36 c := defaultConfig(t) 37 flags := testFlagSet(c) 38 configFileUsed, err := BindPFlags(&FlowConfig{}, flags) 39 require.False(t, configFileUsed) 40 require.Error(t, err) 41 require.True(t, errors.Is(err, errPflagsNotParsed)) 42 }) 43 } 44 45 // TestDefaultConfig ensures the default Flow config is created and returned without errors. 46 func TestDefaultConfig(t *testing.T) { 47 c := defaultConfig(t) 48 require.Equalf(t, "./default-config.yml", c.ConfigFile, "expected default config file to be used") 49 require.NoErrorf(t, c.Validate(), "unexpected error encountered validating default config") 50 unittest.IdentifierFixture() 51 } 52 53 // TestFlowConfig_Validate ensures the Flow validate returns the expected number of validator.ValidationErrors when incorrect 54 // fields are set. 55 func TestFlowConfig_Validate(t *testing.T) { 56 c := defaultConfig(t) 57 // set invalid config values 58 c.NetworkConfig.Unicast.RateLimiter.MessageRateLimit = -100 59 c.NetworkConfig.Unicast.RateLimiter.BandwidthRateLimit = -100 60 err := c.Validate() 61 require.Error(t, err) 62 errs, ok := errors.Unwrap(err).(validator.ValidationErrors) 63 require.True(t, ok) 64 require.Len(t, errs, 2) 65 } 66 67 // TestUnmarshall_UnsetFields ensures that if the config store has any missing config values an error is returned when the config is decoded into a Flow config. 68 func TestUnmarshall_UnsetFields(t *testing.T) { 69 conf = viper.New() 70 c := &FlowConfig{} 71 err := Unmarshall(c) 72 require.True(t, strings.Contains(err.Error(), "has unset fields")) 73 } 74 75 // Test_overrideConfigFile ensures configuration values can be overridden via the --config-file flag. 76 func Test_overrideConfigFile(t *testing.T) { 77 t.Run("should override the default config if --config-file is set", func(t *testing.T) { 78 file, err := os.CreateTemp("", "config-*.yml") 79 require.NoError(t, err) 80 defer os.Remove(file.Name()) 81 82 var data = fmt.Sprintf(`config-file: "%s" 83 network-config: 84 networking-connection-pruning: false 85 `, file.Name()) 86 _, err = file.Write([]byte(data)) 87 require.NoError(t, err) 88 c := defaultConfig(t) 89 flags := testFlagSet(c) 90 err = flags.Set(configFileFlagName, file.Name()) 91 92 require.NoError(t, err) 93 overridden, err := overrideConfigFile(flags) 94 require.NoError(t, err) 95 require.True(t, overridden) 96 97 // ensure config values overridden with values from our inline config 98 require.Equal(t, conf.GetString(configFileFlagName), file.Name()) 99 require.False(t, conf.GetBool("networking-connection-pruning")) 100 }) 101 t.Run("should return an error for missing --config file", func(t *testing.T) { 102 c := defaultConfig(t) 103 flags := testFlagSet(c) 104 err := flags.Set(configFileFlagName, "./missing-config.yml") 105 require.NoError(t, err) 106 overridden, err := overrideConfigFile(flags) 107 require.Error(t, err) 108 require.False(t, overridden) 109 }) 110 t.Run("should not attempt to override config if --config-file is not set", func(t *testing.T) { 111 c := defaultConfig(t) 112 flags := testFlagSet(c) 113 overridden, err := overrideConfigFile(flags) 114 require.NoError(t, err) 115 require.False(t, overridden) 116 }) 117 t.Run("should return an error for file types other than .yml", func(t *testing.T) { 118 file, err := os.CreateTemp("", "config-*.json") 119 require.NoError(t, err) 120 defer os.Remove(file.Name()) 121 c := defaultConfig(t) 122 flags := testFlagSet(c) 123 err = flags.Set(configFileFlagName, file.Name()) 124 require.NoError(t, err) 125 overridden, err := overrideConfigFile(flags) 126 require.Error(t, err) 127 require.False(t, overridden) 128 }) 129 } 130 131 // defaultConfig resets the config store gets the default Flow config. 132 func defaultConfig(t *testing.T) *FlowConfig { 133 initialize() 134 c, err := DefaultConfig() 135 require.NoError(t, err) 136 return c 137 } 138 139 func testFlagSet(c *FlowConfig) *pflag.FlagSet { 140 flags := pflag.NewFlagSet("test", pflag.PanicOnError) 141 // initialize default flags 142 InitializePFlagSet(flags, c) 143 return flags 144 } 145 146 // getAllYAMLKeys is a helper function that recursively extracts all keys from the YAML data. 147 // The keys are returned in the format "prefix-key1-key2-...-keyN". 148 // For example, if the YAML data is: 149 // 150 // key1: 151 // key2: 152 // key3: value 153 // key4: 154 // key5: value 155 // 156 // the function will return ["key1-key2-key3", "key4-key5"]. 157 // Args: 158 // - data: the YAML data. 159 // - prefix: the prefix to prepend to the keys. 160 // Returns: 161 // - the list of keys extracted from the YAML data. 162 func getAllYAMLKeys(data interface{}, prefix string) []string { 163 var keys []string 164 165 switch v := data.(type) { 166 case map[interface{}]interface{}: 167 for key, value := range v { 168 fullKey := prefix + "-" + key.(string) 169 keys = append(keys, getAllYAMLKeys(value, fullKey)...) 170 } 171 case []interface{}: 172 for i, value := range v { 173 fullKey := prefix + "-" + strings.ToLower(strings.ReplaceAll(reflect.TypeOf(value).Name(), "_", "-")) 174 keys = append(keys, getAllYAMLKeys(value, fullKey+string(rune('a'+i)))...) 175 } 176 default: 177 keys = append(keys, strings.TrimRight(prefix, "-")) 178 } 179 180 return keys 181 } 182 183 // allResourceManagerFlagNames is a helper function that returns all libp2p-resource-manager flag names from default-config.yml. 184 func allResourceManagerFlagNames(t *testing.T) []string { 185 yamlFile, err := os.ReadFile("default-config.yml") 186 require.NoError(t, err, "failed to read YAML file") 187 188 var config map[string]interface{} 189 err = yaml.Unmarshal(yamlFile, &config) 190 require.NoError(t, err, "failed to unmarshal YAML file") 191 192 networkConfig, exists := config["network-config"].(map[interface{}]interface{}) 193 require.True(t, exists, "the key 'network-config' does not exist in the YAML file") 194 195 resourceManagerConfig, exists := networkConfig["libp2p-resource-manager"].(map[interface{}]interface{}) 196 require.True(t, exists, "the key 'libp2p-resource-manager' does not exist in the YAML file") 197 198 return getAllYAMLKeys(resourceManagerConfig, "libp2p-resource-manager") 199 } 200 201 // TestAllFlagNames_AllResourceManagerFlags validates that AllFlagNames returns a complete list of flag names from default-config.yml that includes all libp2p-resource-manager flags. 202 func TestAllFlagNames_AllResourceManagerFlags(t *testing.T) { 203 allFlags := netconf.AllFlagNames() 204 for _, f := range allResourceManagerFlagNames(t) { 205 require.Containsf(t, allFlags, f, "the flag '%s' is missing from the list of all flags", f) 206 } 207 } 208 209 // TestLoadLibP2PResourceManagerFlags validates that all libp2p-resource-manager flags from default-config.yml are settable by LoadLibP2PResourceManagerFlags. 210 func TestLoadLibP2PResourceManagerFlags(t *testing.T) { 211 // create an instance of Config to pass to the loader function 212 var config netconf.Config 213 214 // initialize the FlagSet 215 flags := pflag.NewFlagSet(t.Name(), pflag.ContinueOnError) 216 217 // load the flags using your function 218 netconf.LoadLibP2PResourceManagerFlags(flags, &config) 219 220 // retrieve all flag names 221 flagNames := allResourceManagerFlagNames(t) 222 223 // iterate over the flag names to ensure each one is settable 224 for _, flagName := range flagNames { 225 require.NotNil(t, flags.Lookup(flagName), "flag '%s' is not settable by LoadLibP2PResourceManagerFlags", flagName) 226 } 227 }