launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/charm/config_test.go (about) 1 // Copyright 2011, 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charm_test 5 6 import ( 7 "bytes" 8 "fmt" 9 10 gc "launchpad.net/gocheck" 11 12 "launchpad.net/juju-core/charm" 13 ) 14 15 type ConfigSuite struct { 16 config *charm.Config 17 } 18 19 var _ = gc.Suite(&ConfigSuite{}) 20 21 func (s *ConfigSuite) SetUpSuite(c *gc.C) { 22 // Just use a single shared config for the whole suite. There's no use case 23 // for mutating a config, we we assume that nobody will do so here. 24 var err error 25 s.config, err = charm.ReadConfig(bytes.NewBuffer([]byte(` 26 options: 27 title: 28 default: My Title 29 description: A descriptive title used for the service. 30 type: string 31 subtitle: 32 default: "" 33 description: An optional subtitle used for the service. 34 outlook: 35 description: No default outlook. 36 # type defaults to string in python 37 username: 38 default: admin001 39 description: The name of the initial account (given admin permissions). 40 type: string 41 skill-level: 42 description: A number indicating skill. 43 type: int 44 agility-ratio: 45 description: A number from 0 to 1 indicating agility. 46 type: float 47 reticulate-splines: 48 description: Whether to reticulate splines on launch, or not. 49 type: boolean 50 `))) 51 c.Assert(err, gc.IsNil) 52 } 53 54 func (s *ConfigSuite) TestReadSample(c *gc.C) { 55 c.Assert(s.config.Options, gc.DeepEquals, map[string]charm.Option{ 56 "title": { 57 Default: "My Title", 58 Description: "A descriptive title used for the service.", 59 Type: "string", 60 }, 61 "subtitle": { 62 Default: "", 63 Description: "An optional subtitle used for the service.", 64 Type: "string", 65 }, 66 "username": { 67 Default: "admin001", 68 Description: "The name of the initial account (given admin permissions).", 69 Type: "string", 70 }, 71 "outlook": { 72 Description: "No default outlook.", 73 Type: "string", 74 }, 75 "skill-level": { 76 Description: "A number indicating skill.", 77 Type: "int", 78 }, 79 "agility-ratio": { 80 Description: "A number from 0 to 1 indicating agility.", 81 Type: "float", 82 }, 83 "reticulate-splines": { 84 Description: "Whether to reticulate splines on launch, or not.", 85 Type: "boolean", 86 }, 87 }) 88 } 89 90 func (s *ConfigSuite) TestDefaultSettings(c *gc.C) { 91 c.Assert(s.config.DefaultSettings(), gc.DeepEquals, charm.Settings{ 92 "title": "My Title", 93 "subtitle": "", 94 "username": "admin001", 95 "outlook": nil, 96 "skill-level": nil, 97 "agility-ratio": nil, 98 "reticulate-splines": nil, 99 }) 100 } 101 102 func (s *ConfigSuite) TestFilterSettings(c *gc.C) { 103 settings := s.config.FilterSettings(charm.Settings{ 104 "title": "something valid", 105 "username": nil, 106 "unknown": "whatever", 107 "outlook": "", 108 "skill-level": 5.5, 109 "agility-ratio": true, 110 "reticulate-splines": "hullo", 111 }) 112 c.Assert(settings, gc.DeepEquals, charm.Settings{ 113 "title": "something valid", 114 "username": nil, 115 "outlook": "", 116 }) 117 } 118 119 func (s *ConfigSuite) TestValidateSettings(c *gc.C) { 120 for i, test := range []struct { 121 info string 122 input charm.Settings 123 expect charm.Settings 124 err string 125 }{{ 126 info: "nil settings are valid", 127 expect: charm.Settings{}, 128 }, { 129 info: "empty settings are valid", 130 input: charm.Settings{}, 131 }, { 132 info: "unknown keys are not valid", 133 input: charm.Settings{"foo": nil}, 134 err: `unknown option "foo"`, 135 }, { 136 info: "nil is valid for every value type", 137 input: charm.Settings{ 138 "outlook": nil, 139 "skill-level": nil, 140 "agility-ratio": nil, 141 "reticulate-splines": nil, 142 }, 143 }, { 144 info: "correctly-typed values are valid", 145 input: charm.Settings{ 146 "outlook": "stormy", 147 "skill-level": int64(123), 148 "agility-ratio": 0.5, 149 "reticulate-splines": true, 150 }, 151 }, { 152 info: "empty string-typed values stay empty", 153 input: charm.Settings{"outlook": ""}, 154 expect: charm.Settings{"outlook": ""}, 155 }, { 156 info: "almost-correctly-typed values are valid", 157 input: charm.Settings{ 158 "skill-level": 123, 159 "agility-ratio": float32(0.5), 160 }, 161 expect: charm.Settings{ 162 "skill-level": int64(123), 163 "agility-ratio": 0.5, 164 }, 165 }, { 166 info: "bad string", 167 input: charm.Settings{"outlook": false}, 168 err: `option "outlook" expected string, got false`, 169 }, { 170 info: "bad int", 171 input: charm.Settings{"skill-level": 123.4}, 172 err: `option "skill-level" expected int, got 123.4`, 173 }, { 174 info: "bad float", 175 input: charm.Settings{"agility-ratio": "cheese"}, 176 err: `option "agility-ratio" expected float, got "cheese"`, 177 }, { 178 info: "bad boolean", 179 input: charm.Settings{"reticulate-splines": 101}, 180 err: `option "reticulate-splines" expected boolean, got 101`, 181 }} { 182 c.Logf("test %d: %s", i, test.info) 183 result, err := s.config.ValidateSettings(test.input) 184 if test.err != "" { 185 c.Check(err, gc.ErrorMatches, test.err) 186 } else { 187 c.Check(err, gc.IsNil) 188 if test.expect == nil { 189 c.Check(result, gc.DeepEquals, test.input) 190 } else { 191 c.Check(result, gc.DeepEquals, test.expect) 192 } 193 } 194 } 195 } 196 197 var settingsWithNils = charm.Settings{ 198 "outlook": nil, 199 "skill-level": nil, 200 "agility-ratio": nil, 201 "reticulate-splines": nil, 202 } 203 204 var settingsWithValues = charm.Settings{ 205 "outlook": "whatever", 206 "skill-level": int64(123), 207 "agility-ratio": 2.22, 208 "reticulate-splines": true, 209 } 210 211 func (s *ConfigSuite) TestParseSettingsYAML(c *gc.C) { 212 for i, test := range []struct { 213 info string 214 yaml string 215 key string 216 expect charm.Settings 217 err string 218 }{{ 219 info: "bad structure", 220 yaml: "`", 221 err: `cannot parse settings data: .*`, 222 }, { 223 info: "bad key", 224 yaml: "{}", 225 key: "blah", 226 err: `no settings found for "blah"`, 227 }, { 228 info: "bad settings key", 229 yaml: "blah:\n ping: pong", 230 key: "blah", 231 err: `unknown option "ping"`, 232 }, { 233 info: "bad type for string", 234 yaml: "blah:\n outlook: 123", 235 key: "blah", 236 err: `option "outlook" expected string, got 123`, 237 }, { 238 info: "bad type for int", 239 yaml: "blah:\n skill-level: 12.345", 240 key: "blah", 241 err: `option "skill-level" expected int, got 12.345`, 242 }, { 243 info: "bad type for float", 244 yaml: "blah:\n agility-ratio: blob", 245 key: "blah", 246 err: `option "agility-ratio" expected float, got "blob"`, 247 }, { 248 info: "bad type for boolean", 249 yaml: "blah:\n reticulate-splines: 123", 250 key: "blah", 251 err: `option "reticulate-splines" expected boolean, got 123`, 252 }, { 253 info: "bad string for int", 254 yaml: "blah:\n skill-level: cheese", 255 key: "blah", 256 err: `option "skill-level" expected int, got "cheese"`, 257 }, { 258 info: "bad string for float", 259 yaml: "blah:\n agility-ratio: blob", 260 key: "blah", 261 err: `option "agility-ratio" expected float, got "blob"`, 262 }, { 263 info: "bad string for boolean", 264 yaml: "blah:\n reticulate-splines: cannonball", 265 key: "blah", 266 err: `option "reticulate-splines" expected boolean, got "cannonball"`, 267 }, { 268 info: "empty dict is valid", 269 yaml: "blah: {}", 270 key: "blah", 271 expect: charm.Settings{}, 272 }, { 273 info: "nil values are valid", 274 yaml: `blah: 275 outlook: null 276 skill-level: null 277 agility-ratio: null 278 reticulate-splines: null`, 279 key: "blah", 280 expect: settingsWithNils, 281 }, { 282 info: "empty strings for bool options are not accepted", 283 yaml: `blah: 284 outlook: "" 285 skill-level: 123 286 agility-ratio: 12.0 287 reticulate-splines: ""`, 288 key: "blah", 289 err: `option "reticulate-splines" expected boolean, got ""`, 290 }, { 291 info: "empty strings for int options are not accepted", 292 yaml: `blah: 293 outlook: "" 294 skill-level: "" 295 agility-ratio: 12.0 296 reticulate-splines: false`, 297 key: "blah", 298 err: `option "skill-level" expected int, got ""`, 299 }, { 300 info: "empty strings for float options are not accepted", 301 yaml: `blah: 302 outlook: "" 303 skill-level: 123 304 agility-ratio: "" 305 reticulate-splines: false`, 306 key: "blah", 307 err: `option "agility-ratio" expected float, got ""`, 308 }, { 309 info: "appropriate strings are valid", 310 yaml: `blah: 311 outlook: whatever 312 skill-level: "123" 313 agility-ratio: "2.22" 314 reticulate-splines: "true"`, 315 key: "blah", 316 expect: settingsWithValues, 317 }, { 318 info: "appropriate types are valid", 319 yaml: `blah: 320 outlook: whatever 321 skill-level: 123 322 agility-ratio: 2.22 323 reticulate-splines: y`, 324 key: "blah", 325 expect: settingsWithValues, 326 }} { 327 c.Logf("test %d: %s", i, test.info) 328 result, err := s.config.ParseSettingsYAML([]byte(test.yaml), test.key) 329 if test.err != "" { 330 c.Check(err, gc.ErrorMatches, test.err) 331 } else { 332 c.Check(err, gc.IsNil) 333 c.Check(result, gc.DeepEquals, test.expect) 334 } 335 } 336 } 337 338 func (s *ConfigSuite) TestParseSettingsStrings(c *gc.C) { 339 for i, test := range []struct { 340 info string 341 input map[string]string 342 expect charm.Settings 343 err string 344 }{{ 345 info: "nil map is valid", 346 expect: charm.Settings{}, 347 }, { 348 info: "empty map is valid", 349 input: map[string]string{}, 350 expect: charm.Settings{}, 351 }, { 352 info: "empty strings for string options are valid", 353 input: map[string]string{"outlook": ""}, 354 expect: charm.Settings{"outlook": ""}, 355 }, { 356 info: "empty strings for non-string options are invalid", 357 input: map[string]string{"skill-level": ""}, 358 err: `option "skill-level" expected int, got ""`, 359 }, { 360 info: "strings are converted", 361 input: map[string]string{ 362 "outlook": "whatever", 363 "skill-level": "123", 364 "agility-ratio": "2.22", 365 "reticulate-splines": "true", 366 }, 367 expect: settingsWithValues, 368 }, { 369 info: "bad string for int", 370 input: map[string]string{"skill-level": "cheese"}, 371 err: `option "skill-level" expected int, got "cheese"`, 372 }, { 373 info: "bad string for float", 374 input: map[string]string{"agility-ratio": "blob"}, 375 err: `option "agility-ratio" expected float, got "blob"`, 376 }, { 377 info: "bad string for boolean", 378 input: map[string]string{"reticulate-splines": "cannonball"}, 379 err: `option "reticulate-splines" expected boolean, got "cannonball"`, 380 }} { 381 c.Logf("test %d: %s", i, test.info) 382 result, err := s.config.ParseSettingsStrings(test.input) 383 if test.err != "" { 384 c.Check(err, gc.ErrorMatches, test.err) 385 } else { 386 c.Check(err, gc.IsNil) 387 c.Check(result, gc.DeepEquals, test.expect) 388 } 389 } 390 } 391 392 func (s *ConfigSuite) TestConfigError(c *gc.C) { 393 _, err := charm.ReadConfig(bytes.NewBuffer([]byte(`options: {t: {type: foo}}`))) 394 c.Assert(err, gc.ErrorMatches, `invalid config: option "t" has unknown type "foo"`) 395 } 396 397 func (s *ConfigSuite) TestDefaultType(c *gc.C) { 398 assertDefault := func(type_ string, value string, expected interface{}) { 399 config := fmt.Sprintf(`options: {t: {type: %s, default: %s}}`, type_, value) 400 result, err := charm.ReadConfig(bytes.NewBuffer([]byte(config))) 401 c.Assert(err, gc.IsNil) 402 c.Assert(result.Options["t"].Default, gc.Equals, expected) 403 } 404 405 assertDefault("boolean", "true", true) 406 assertDefault("string", "golden grahams", "golden grahams") 407 assertDefault("string", `""`, "") 408 assertDefault("float", "2.2e11", 2.2e11) 409 assertDefault("int", "99", int64(99)) 410 411 assertTypeError := func(type_, str, value string) { 412 config := fmt.Sprintf(`options: {t: {type: %s, default: %s}}`, type_, str) 413 _, err := charm.ReadConfig(bytes.NewBuffer([]byte(config))) 414 expected := fmt.Sprintf(`invalid config default: option "t" expected %s, got %s`, type_, value) 415 c.Assert(err, gc.ErrorMatches, expected) 416 } 417 418 assertTypeError("boolean", "henry", `"henry"`) 419 assertTypeError("string", "2.5", "2.5") 420 assertTypeError("float", "123", "123") 421 assertTypeError("int", "true", "true") 422 } 423 424 // When an empty config is supplied an error should be returned 425 func (s *ConfigSuite) TestEmptyConfigReturnsError(c *gc.C) { 426 config := "" 427 result, err := charm.ReadConfig(bytes.NewBuffer([]byte(config))) 428 c.Assert(result, gc.IsNil) 429 c.Assert(err, gc.ErrorMatches, "invalid config: empty configuration") 430 }