34
45
reticulate-splines:
35
46
description: Whether to reticulate splines on launch, or not.
39
func repoConfig(name string) io.Reader {
40
charmDir := testing.Charms.DirPath(name)
41
file, err := os.Open(filepath.Join(charmDir, "config.yaml"))
46
data, err := ioutil.ReadAll(file)
50
return bytes.NewBuffer(data)
53
type ConfigSuite struct{}
55
var _ = Suite(&ConfigSuite{})
57
func (s *ConfigSuite) TestReadConfig(c *C) {
58
config, err := charm.ReadConfig(repoConfig("dummy"))
59
49
c.Assert(err, IsNil)
60
c.Assert(config.Options["title"], DeepEquals,
52
func (s *ConfigSuite) TestReadSample(c *C) {
53
c.Assert(s.config.Options, DeepEquals, map[string]charm.Option{
62
55
Default: "My Title",
63
56
Description: "A descriptive title used for the service.",
61
Description: "An optional subtitle used for the service.",
66
Description: "The name of the initial account (given admin permissions).",
70
Description: "No default outlook.",
74
Description: "A number indicating skill.",
78
Description: "A number from 0 to 1 indicating agility.",
81
"reticulate-splines": {
82
Description: "Whether to reticulate splines on launch, or not.",
88
func (s *ConfigSuite) TestDefaultSettings(c *C) {
89
c.Assert(s.config.DefaultSettings(), DeepEquals, charm.Settings{
92
"username": "admin001",
96
"reticulate-splines": nil,
100
func (s *ConfigSuite) TestFilterSettings(c *C) {
101
settings := s.config.FilterSettings(charm.Settings{
102
"title": "something valid",
104
"unknown": "whatever",
107
"agility-ratio": true,
108
"reticulate-splines": "hullo",
110
c.Assert(settings, DeepEquals, charm.Settings{
111
"title": "something valid",
117
func (s *ConfigSuite) TestValidateSettings(c *C) {
118
for i, test := range []struct {
121
expect charm.Settings
124
info: "nil settings are valid",
125
expect: charm.Settings{},
127
info: "empty settings are valid",
128
input: charm.Settings{},
130
info: "unknown keys are not valid",
131
input: charm.Settings{"foo": nil},
132
err: `unknown option "foo"`,
134
info: "nil is valid for every value type",
135
input: charm.Settings{
138
"agility-ratio": nil,
139
"reticulate-splines": nil,
142
info: "correctly-typed values are valid",
143
input: charm.Settings{
145
"skill-level": int64(123),
146
"agility-ratio": 0.5,
147
"reticulate-splines": true,
150
info: "empty string-typed values become nil",
151
input: charm.Settings{"outlook": ""},
152
expect: charm.Settings{"outlook": nil},
154
info: "almost-correctly-typed values are valid",
155
input: charm.Settings{
157
"agility-ratio": float32(0.5),
159
expect: charm.Settings{
160
"skill-level": int64(123),
161
"agility-ratio": 0.5,
165
input: charm.Settings{"outlook": false},
166
err: `option "outlook" expected string, got false`,
169
input: charm.Settings{"skill-level": 123.4},
170
err: `option "skill-level" expected int, got 123.4`,
173
input: charm.Settings{"agility-ratio": "cheese"},
174
err: `option "agility-ratio" expected float, got "cheese"`,
177
input: charm.Settings{"reticulate-splines": 101},
178
err: `option "reticulate-splines" expected boolean, got 101`,
180
c.Logf("test %d: %s", i, test.info)
181
result, err := s.config.ValidateSettings(test.input)
183
c.Check(err, ErrorMatches, test.err)
186
if test.expect == nil {
187
c.Check(result, DeepEquals, test.input)
189
c.Check(result, DeepEquals, test.expect)
195
var settingsWithNils = charm.Settings{
198
"agility-ratio": nil,
199
"reticulate-splines": nil,
202
var settingsWithValues = charm.Settings{
203
"outlook": "whatever",
204
"skill-level": int64(123),
205
"agility-ratio": 2.22,
206
"reticulate-splines": true,
209
func (s *ConfigSuite) TestParseSettingsYAML(c *C) {
210
for i, test := range []struct {
214
expect charm.Settings
217
info: "bad structure",
219
err: `cannot parse settings data: .*`,
224
err: `no settings found for "blah"`,
226
info: "bad settings key",
227
yaml: "blah:\n ping: pong",
229
err: `unknown option "ping"`,
231
info: "bad type for string",
232
yaml: "blah:\n outlook: 123",
234
err: `option "outlook" expected string, got 123`,
236
info: "bad type for int",
237
yaml: "blah:\n skill-level: 12.345",
239
err: `option "skill-level" expected int, got 12.345`,
241
info: "bad type for float",
242
yaml: "blah:\n agility-ratio: blob",
244
err: `option "agility-ratio" expected float, got "blob"`,
246
info: "bad type for boolean",
247
yaml: "blah:\n reticulate-splines: 123",
249
err: `option "reticulate-splines" expected boolean, got 123`,
251
info: "bad string for int",
252
yaml: "blah:\n skill-level: cheese",
254
err: `option "skill-level" expected int, got "cheese"`,
256
info: "bad string for float",
257
yaml: "blah:\n agility-ratio: blob",
259
err: `option "agility-ratio" expected float, got "blob"`,
261
info: "bad string for boolean",
262
yaml: "blah:\n reticulate-splines: cannonball",
264
err: `option "reticulate-splines" expected boolean, got "cannonball"`,
266
info: "empty dict is valid",
269
expect: charm.Settings{},
271
info: "nil values are valid",
276
reticulate-splines: null`,
278
expect: settingsWithNils,
280
info: "empty strings are considered nil",
285
reticulate-splines: ""`,
287
expect: settingsWithNils,
289
info: "appropriate strings are valid",
293
agility-ratio: "2.22"
294
reticulate-splines: "true"`,
296
expect: settingsWithValues,
298
info: "appropriate types are valid",
303
reticulate-splines: y`,
305
expect: settingsWithValues,
307
c.Logf("test %d: %s", i, test.info)
308
result, err := s.config.ParseSettingsYAML([]byte(test.yaml), test.key)
310
c.Check(err, ErrorMatches, test.err)
313
c.Check(result, DeepEquals, test.expect)
318
func (s *ConfigSuite) TestParseSettingsStrings(c *C) {
319
for i, test := range []struct {
321
input map[string]string
322
expect charm.Settings
325
info: "nil map is valid",
326
expect: charm.Settings{},
328
info: "empty map is valid",
329
input: map[string]string{},
330
expect: charm.Settings{},
332
info: "empty strings are nil values",
333
input: map[string]string{
337
"reticulate-splines": "",
339
expect: settingsWithNils,
341
info: "strings are converted",
342
input: map[string]string{
343
"outlook": "whatever",
344
"skill-level": "123",
345
"agility-ratio": "2.22",
346
"reticulate-splines": "true",
348
expect: settingsWithValues,
350
info: "bad string for int",
351
input: map[string]string{"skill-level": "cheese"},
352
err: `option "skill-level" expected int, got "cheese"`,
354
info: "bad string for float",
355
input: map[string]string{"agility-ratio": "blob"},
356
err: `option "agility-ratio" expected float, got "blob"`,
358
info: "bad string for boolean",
359
input: map[string]string{"reticulate-splines": "cannonball"},
360
err: `option "reticulate-splines" expected boolean, got "cannonball"`,
362
c.Logf("test %d: %s", i, test.info)
363
result, err := s.config.ParseSettingsStrings(test.input)
365
c.Check(err, ErrorMatches, test.err)
368
c.Check(result, DeepEquals, test.expect)
69
373
func (s *ConfigSuite) TestConfigError(c *C) {
70
374
_, err := charm.ReadConfig(bytes.NewBuffer([]byte(`options: {t: {type: foo}}`)))
71
c.Assert(err, ErrorMatches, `config: options.t.type: unexpected value.*`)
375
c.Assert(err, ErrorMatches, `invalid config: option "t" has unknown type "foo"`)
74
378
func (s *ConfigSuite) TestDefaultType(c *C) {
82
386
assertDefault("boolean", "true", true)
83
387
assertDefault("string", "golden grahams", "golden grahams")
388
assertDefault("string", `""`, "")
84
389
assertDefault("float", "2.2e11", 2.2e11)
85
390
assertDefault("int", "99", int64(99))
87
assertTypeError := func(type_ string, value string) {
88
config := fmt.Sprintf(`options: {t: {type: %s, default: %s}}`, type_, value)
392
assertTypeError := func(type_, str, value string) {
393
config := fmt.Sprintf(`options: {t: {type: %s, default: %s}}`, type_, str)
89
394
_, err := charm.ReadConfig(bytes.NewBuffer([]byte(config)))
90
expected := fmt.Sprintf(`Bad default for "t": %s is not of type %s`, value, type_)
395
expected := fmt.Sprintf(`invalid config default: option "t" expected %s, got %s`, type_, value)
91
396
c.Assert(err, ErrorMatches, expected)
94
assertTypeError("boolean", "henry")
95
assertTypeError("string", "2.5")
96
assertTypeError("float", "blob")
97
assertTypeError("int", "33.2")
100
func (s *ConfigSuite) TestParseSample(c *C) {
101
config, err := charm.ReadConfig(bytes.NewBuffer([]byte(sampleConfig)))
104
opt := config.Options
105
c.Assert(opt["title"], DeepEquals,
108
Description: "A descriptive title used for the service.",
112
c.Assert(opt["outlook"], DeepEquals,
114
Description: "No default outlook.",
118
c.Assert(opt["username"], DeepEquals,
121
Description: "The name of the initial account (given admin permissions).",
125
c.Assert(opt["skill-level"], DeepEquals,
127
Description: "A number indicating skill.",
131
c.Assert(opt["reticulate-splines"], DeepEquals,
133
Description: "Whether to reticulate splines on launch, or not.",
139
func (s *ConfigSuite) TestValidate(c *C) {
140
config, err := charm.ReadConfig(bytes.NewBuffer([]byte(sampleConfig)))
143
input := map[string]string{
144
"title": "Helpful Title",
148
// This should include an overridden value, a default and a new value.
149
expected := map[string]interface{}{
150
"title": "Helpful Title",
152
"username": "admin001",
155
output, err := config.Validate(input)
157
c.Assert(output, DeepEquals, expected)
159
// Check whether float conversion is working.
160
input["agility-ratio"] = "0.5"
161
input["skill-level"] = "7"
162
expected["agility-ratio"] = 0.5
163
expected["skill-level"] = int64(7)
164
output, err = config.Validate(input)
166
c.Assert(output, DeepEquals, expected)
168
// Check whether float errors are caught.
169
input["agility-ratio"] = "foo"
170
output, err = config.Validate(input)
171
c.Assert(err, ErrorMatches, `Value for "agility-ratio" is not a float: "foo"`)
172
input["agility-ratio"] = "0.5"
174
// Check whether int errors are caught.
175
input["skill-level"] = "foo"
176
output, err = config.Validate(input)
177
c.Assert(err, ErrorMatches, `Value for "skill-level" is not an int: "foo"`)
178
input["skill-level"] = "7"
180
// Check whether boolean errors are caught.
181
input["reticulate-splines"] = "maybe"
182
output, err = config.Validate(input)
183
c.Assert(err, ErrorMatches, `Value for "reticulate-splines" is not a boolean: "maybe"`)
184
input["reticulate-splines"] = "false"
186
// Now try to set a value outside the expected.
187
input["bad"] = "value"
188
output, err = config.Validate(input)
189
c.Assert(output, IsNil)
190
c.Assert(err, ErrorMatches, `Unknown configuration option: "bad"`)
193
var convertTests = []struct {
195
input map[string]interface{}
196
expect map[string]interface{}
199
// Schema defaults are ignored.
200
summary: "valid strings",
201
input: map[string]interface{}{
202
"title": "Helpful Title",
205
expect: map[string]interface{}{
206
"title": "Helpful Title",
210
// Integers are always int64 in YAML, where the input is usually coming from.
211
summary: "valid integers and floats",
212
input: map[string]interface{}{
213
"agility-ratio": 0.5,
214
"skill-level": int64(7),
216
expect: map[string]interface{}{
217
"agility-ratio": 0.5,
218
"skill-level": int64(7),
221
summary: "valid booleans",
222
input: map[string]interface{}{"reticulate-splines": true},
223
expect: map[string]interface{}{"reticulate-splines": true},
225
summary: "invalid type error with floats",
226
input: map[string]interface{}{"agility-ratio": "bar"},
227
err: `unexpected type in service configuration "agility-ratio"="bar"; expected float`,
229
summary: "invalid type error with integers",
230
input: map[string]interface{}{"skill-level": "foo"},
231
err: `unexpected type in service configuration "skill-level"="foo"; expected int`,
233
summary: "invalid type error with booleans",
234
input: map[string]interface{}{"reticulate-splines": "maybe"},
235
err: `unexpected type in service configuration "reticulate-splines"="maybe"; expected boolean`,
237
summary: "with value not in the schema (ignored)",
238
input: map[string]interface{}{"bad": "value"},
239
expect: map[string]interface{}{},
242
func (s *ConfigSuite) TestConvert(c *C) {
243
config, err := charm.ReadConfig(bytes.NewBuffer([]byte(sampleConfig)))
246
for i, t := range convertTests {
247
c.Logf("test %d: %s", i, t.summary)
248
output, err := config.Convert(t.input)
250
c.Assert(err, ErrorMatches, t.err)
253
c.Assert(output, DeepEquals, t.expect)
399
assertTypeError("boolean", "henry", `"henry"`)
400
assertTypeError("string", "2.5", "2.5")
401
assertTypeError("float", "123", "123")
402
assertTypeError("int", "true", "true")