launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/constraints/constraints_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package constraints_test 5 6 import ( 7 "encoding/json" 8 "testing" 9 10 gc "launchpad.net/gocheck" 11 "launchpad.net/goyaml" 12 13 "launchpad.net/juju-core/constraints" 14 "launchpad.net/juju-core/instance" 15 jc "launchpad.net/juju-core/testing/checkers" 16 ) 17 18 func TestPackage(t *testing.T) { 19 gc.TestingT(t) 20 } 21 22 type ConstraintsSuite struct{} 23 24 var _ = gc.Suite(&ConstraintsSuite{}) 25 26 var parseConstraintsTests = []struct { 27 summary string 28 args []string 29 err string 30 }{ 31 // Simple errors. 32 { 33 summary: "nothing at all", 34 }, { 35 summary: "empty", 36 args: []string{" "}, 37 }, { 38 summary: "complete nonsense", 39 args: []string{"cheese"}, 40 err: `malformed constraint "cheese"`, 41 }, { 42 summary: "missing name", 43 args: []string{"=cheese"}, 44 err: `malformed constraint "=cheese"`, 45 }, { 46 summary: "unknown constraint", 47 args: []string{"cheese=edam"}, 48 err: `unknown constraint "cheese"`, 49 }, 50 51 // "container" in detail. 52 { 53 summary: "set container empty", 54 args: []string{"container="}, 55 }, { 56 summary: "set container to none", 57 args: []string{"container=none"}, 58 }, { 59 summary: "set container lxc", 60 args: []string{"container=lxc"}, 61 }, { 62 summary: "set nonsense container", 63 args: []string{"container=foo"}, 64 err: `bad "container" constraint: invalid container type "foo"`, 65 }, { 66 summary: "double set container together", 67 args: []string{"container=lxc container=lxc"}, 68 err: `bad "container" constraint: already set`, 69 }, { 70 summary: "double set container separately", 71 args: []string{"container=lxc", "container="}, 72 err: `bad "container" constraint: already set`, 73 }, 74 75 // "arch" in detail. 76 { 77 summary: "set arch empty", 78 args: []string{"arch="}, 79 }, { 80 summary: "set arch amd64", 81 args: []string{"arch=amd64"}, 82 }, { 83 summary: "set arch i386", 84 args: []string{"arch=i386"}, 85 }, { 86 summary: "set arch arm", 87 args: []string{"arch=arm"}, 88 }, { 89 summary: "set nonsense arch 1", 90 args: []string{"arch=cheese"}, 91 err: `bad "arch" constraint: "cheese" not recognized`, 92 }, { 93 summary: "set nonsense arch 2", 94 args: []string{"arch=123.45"}, 95 err: `bad "arch" constraint: "123.45" not recognized`, 96 }, { 97 summary: "double set arch together", 98 args: []string{"arch=amd64 arch=amd64"}, 99 err: `bad "arch" constraint: already set`, 100 }, { 101 summary: "double set arch separately", 102 args: []string{"arch=arm", "arch="}, 103 err: `bad "arch" constraint: already set`, 104 }, 105 106 // "cpu-cores" in detail. 107 { 108 summary: "set cpu-cores empty", 109 args: []string{"cpu-cores="}, 110 }, { 111 summary: "set cpu-cores zero", 112 args: []string{"cpu-cores=0"}, 113 }, { 114 summary: "set cpu-cores", 115 args: []string{"cpu-cores=4"}, 116 }, { 117 summary: "set nonsense cpu-cores 1", 118 args: []string{"cpu-cores=cheese"}, 119 err: `bad "cpu-cores" constraint: must be a non-negative integer`, 120 }, { 121 summary: "set nonsense cpu-cores 2", 122 args: []string{"cpu-cores=-1"}, 123 err: `bad "cpu-cores" constraint: must be a non-negative integer`, 124 }, { 125 summary: "set nonsense cpu-cores 3", 126 args: []string{"cpu-cores=123.45"}, 127 err: `bad "cpu-cores" constraint: must be a non-negative integer`, 128 }, { 129 summary: "double set cpu-cores together", 130 args: []string{"cpu-cores=128 cpu-cores=1"}, 131 err: `bad "cpu-cores" constraint: already set`, 132 }, { 133 summary: "double set cpu-cores separately", 134 args: []string{"cpu-cores=128", "cpu-cores=1"}, 135 err: `bad "cpu-cores" constraint: already set`, 136 }, 137 138 // "cpu-power" in detail. 139 { 140 summary: "set cpu-power empty", 141 args: []string{"cpu-power="}, 142 }, { 143 summary: "set cpu-power zero", 144 args: []string{"cpu-power=0"}, 145 }, { 146 summary: "set cpu-power", 147 args: []string{"cpu-power=44"}, 148 }, { 149 summary: "set nonsense cpu-power 1", 150 args: []string{"cpu-power=cheese"}, 151 err: `bad "cpu-power" constraint: must be a non-negative integer`, 152 }, { 153 summary: "set nonsense cpu-power 2", 154 args: []string{"cpu-power=-1"}, 155 err: `bad "cpu-power" constraint: must be a non-negative integer`, 156 }, { 157 summary: "double set cpu-power together", 158 args: []string{" cpu-power=300 cpu-power=1700 "}, 159 err: `bad "cpu-power" constraint: already set`, 160 }, { 161 summary: "double set cpu-power separately", 162 args: []string{"cpu-power=300 ", " cpu-power=1700"}, 163 err: `bad "cpu-power" constraint: already set`, 164 }, 165 166 // "mem" in detail. 167 { 168 summary: "set mem empty", 169 args: []string{"mem="}, 170 }, { 171 summary: "set mem zero", 172 args: []string{"mem=0"}, 173 }, { 174 summary: "set mem without suffix", 175 args: []string{"mem=512"}, 176 }, { 177 summary: "set mem with M suffix", 178 args: []string{"mem=512M"}, 179 }, { 180 summary: "set mem with G suffix", 181 args: []string{"mem=1.5G"}, 182 }, { 183 summary: "set mem with T suffix", 184 args: []string{"mem=36.2T"}, 185 }, { 186 summary: "set mem with P suffix", 187 args: []string{"mem=18.9P"}, 188 }, { 189 summary: "set nonsense mem 1", 190 args: []string{"mem=cheese"}, 191 err: `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`, 192 }, { 193 summary: "set nonsense mem 2", 194 args: []string{"mem=-1"}, 195 err: `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`, 196 }, { 197 summary: "set nonsense mem 3", 198 args: []string{"mem=32Y"}, 199 err: `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`, 200 }, { 201 summary: "double set mem together", 202 args: []string{"mem=1G mem=2G"}, 203 err: `bad "mem" constraint: already set`, 204 }, { 205 summary: "double set mem separately", 206 args: []string{"mem=1G", "mem=2G"}, 207 err: `bad "mem" constraint: already set`, 208 }, 209 210 // "root-disk" in detail. 211 { 212 summary: "set root-disk empty", 213 args: []string{"root-disk="}, 214 }, { 215 summary: "set root-disk zero", 216 args: []string{"root-disk=0"}, 217 }, { 218 summary: "set root-disk without suffix", 219 args: []string{"root-disk=512"}, 220 }, { 221 summary: "set root-disk with M suffix", 222 args: []string{"root-disk=512M"}, 223 }, { 224 summary: "set root-disk with G suffix", 225 args: []string{"root-disk=1.5G"}, 226 }, { 227 summary: "set root-disk with T suffix", 228 args: []string{"root-disk=36.2T"}, 229 }, { 230 summary: "set root-disk with P suffix", 231 args: []string{"root-disk=18.9P"}, 232 }, { 233 summary: "set nonsense root-disk 1", 234 args: []string{"root-disk=cheese"}, 235 err: `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`, 236 }, { 237 summary: "set nonsense root-disk 2", 238 args: []string{"root-disk=-1"}, 239 err: `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`, 240 }, { 241 summary: "set nonsense root-disk 3", 242 args: []string{"root-disk=32Y"}, 243 err: `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`, 244 }, { 245 summary: "double set root-disk together", 246 args: []string{"root-disk=1G root-disk=2G"}, 247 err: `bad "root-disk" constraint: already set`, 248 }, { 249 summary: "double set root-disk separately", 250 args: []string{"root-disk=1G", "root-disk=2G"}, 251 err: `bad "root-disk" constraint: already set`, 252 }, 253 254 // tags 255 { 256 summary: "single tag", 257 args: []string{"tags=foo"}, 258 }, { 259 summary: "multiple tags", 260 args: []string{"tags=foo,bar"}, 261 }, { 262 summary: "no tags", 263 args: []string{"tags="}, 264 }, 265 266 // Everything at once. 267 { 268 summary: "kitchen sink together", 269 args: []string{" root-disk=8G mem=2T arch=i386 cpu-cores=4096 cpu-power=9001 container=lxc tags=foo,bar"}, 270 }, { 271 summary: "kitchen sink separately", 272 args: []string{"root-disk=8G", "mem=2T", "cpu-cores=4096", "cpu-power=9001", "arch=arm", "container=lxc", "tags=foo,bar"}, 273 }, 274 } 275 276 func (s *ConstraintsSuite) TestParseConstraints(c *gc.C) { 277 for i, t := range parseConstraintsTests { 278 c.Logf("test %d: %s", i, t.summary) 279 cons0, err := constraints.Parse(t.args...) 280 if t.err == "" { 281 c.Assert(err, gc.IsNil) 282 } else { 283 c.Assert(err, gc.ErrorMatches, t.err) 284 continue 285 } 286 cons1, err := constraints.Parse(cons0.String()) 287 c.Check(err, gc.IsNil) 288 c.Check(cons1, gc.DeepEquals, cons0) 289 } 290 } 291 292 func (s *ConstraintsSuite) TestParseMissingTags(c *gc.C) { 293 con := constraints.MustParse("arch=amd64 mem=4G cpu-cores=1 root-disk=8G") 294 c.Check(con.Tags, gc.IsNil) 295 } 296 297 func (s *ConstraintsSuite) TestParseNoTags(c *gc.C) { 298 con := constraints.MustParse("arch=amd64 mem=4G cpu-cores=1 root-disk=8G tags=") 299 c.Assert(con.Tags, gc.Not(gc.IsNil)) 300 c.Check(*con.Tags, gc.HasLen, 0) 301 } 302 303 func (s *ConstraintsSuite) TestIsEmpty(c *gc.C) { 304 con := constraints.Value{} 305 c.Check(&con, jc.Satisfies, constraints.IsEmpty) 306 con = constraints.MustParse("arch=amd64") 307 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 308 con = constraints.MustParse("") 309 c.Check(&con, jc.Satisfies, constraints.IsEmpty) 310 con = constraints.MustParse("tags=") 311 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 312 con = constraints.MustParse("mem=") 313 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 314 con = constraints.MustParse("arch=") 315 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 316 con = constraints.MustParse("root-disk=") 317 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 318 con = constraints.MustParse("cpu-power=") 319 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 320 con = constraints.MustParse("cpu-cores=") 321 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 322 con = constraints.MustParse("container=") 323 c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) 324 } 325 326 func uint64p(i uint64) *uint64 { 327 return &i 328 } 329 330 func strp(s string) *string { 331 return &s 332 } 333 334 func ctypep(ctype string) *instance.ContainerType { 335 res := instance.ContainerType(ctype) 336 return &res 337 } 338 339 type roundTrip struct { 340 Name string 341 Value constraints.Value 342 } 343 344 var constraintsRoundtripTests = []roundTrip{ 345 {"empty", constraints.Value{}}, 346 {"Arch1", constraints.Value{Arch: strp("")}}, 347 {"Arch2", constraints.Value{Arch: strp("amd64")}}, 348 {"Container1", constraints.Value{Container: ctypep("")}}, 349 {"Container2", constraints.Value{Container: ctypep("lxc")}}, 350 {"Container3", constraints.Value{Container: nil}}, 351 {"CpuCores1", constraints.Value{CpuCores: nil}}, 352 {"CpuCores2", constraints.Value{CpuCores: uint64p(0)}}, 353 {"CpuCores3", constraints.Value{CpuCores: uint64p(128)}}, 354 {"CpuPower1", constraints.Value{CpuPower: nil}}, 355 {"CpuPower2", constraints.Value{CpuPower: uint64p(0)}}, 356 {"CpuPower3", constraints.Value{CpuPower: uint64p(250)}}, 357 {"Mem1", constraints.Value{Mem: nil}}, 358 {"Mem2", constraints.Value{Mem: uint64p(0)}}, 359 {"Mem3", constraints.Value{Mem: uint64p(98765)}}, 360 {"RootDisk1", constraints.Value{RootDisk: nil}}, 361 {"RootDisk2", constraints.Value{RootDisk: uint64p(0)}}, 362 {"RootDisk2", constraints.Value{RootDisk: uint64p(109876)}}, 363 {"Tags1", constraints.Value{Tags: nil}}, 364 {"Tags2", constraints.Value{Tags: &[]string{}}}, 365 {"Tags3", constraints.Value{Tags: &[]string{"foo", "bar"}}}, 366 {"All", constraints.Value{ 367 Arch: strp("i386"), 368 Container: ctypep("lxc"), 369 CpuCores: uint64p(4096), 370 CpuPower: uint64p(9001), 371 Mem: uint64p(18000000000), 372 RootDisk: uint64p(24000000000), 373 Tags: &[]string{"foo", "bar"}, 374 }}, 375 } 376 377 func (s *ConstraintsSuite) TestRoundtripGnuflagValue(c *gc.C) { 378 for _, t := range constraintsRoundtripTests { 379 c.Logf("test %s", t.Name) 380 var cons constraints.Value 381 val := constraints.ConstraintsValue{&cons} 382 err := val.Set(t.Value.String()) 383 c.Check(err, gc.IsNil) 384 c.Check(cons, gc.DeepEquals, t.Value) 385 } 386 } 387 388 func (s *ConstraintsSuite) TestRoundtripString(c *gc.C) { 389 for _, t := range constraintsRoundtripTests { 390 c.Logf("test %s", t.Name) 391 cons, err := constraints.Parse(t.Value.String()) 392 c.Check(err, gc.IsNil) 393 c.Check(cons, gc.DeepEquals, t.Value) 394 } 395 } 396 397 func (s *ConstraintsSuite) TestRoundtripJson(c *gc.C) { 398 for _, t := range constraintsRoundtripTests { 399 c.Logf("test %s", t.Name) 400 data, err := json.Marshal(t.Value) 401 c.Assert(err, gc.IsNil) 402 var cons constraints.Value 403 err = json.Unmarshal(data, &cons) 404 c.Check(err, gc.IsNil) 405 c.Check(cons, gc.DeepEquals, t.Value) 406 } 407 } 408 409 func (s *ConstraintsSuite) TestRoundtripYaml(c *gc.C) { 410 for _, t := range constraintsRoundtripTests { 411 c.Logf("test %s", t.Name) 412 data, err := goyaml.Marshal(t.Value) 413 c.Assert(err, gc.IsNil) 414 var cons constraints.Value 415 err = goyaml.Unmarshal(data, &cons) 416 c.Check(err, gc.IsNil) 417 c.Check(cons, gc.DeepEquals, t.Value) 418 } 419 } 420 421 var withFallbacksTests = []struct { 422 desc string 423 initial string 424 fallbacks string 425 final string 426 }{ 427 { 428 desc: "empty all round", 429 }, { 430 desc: "container with empty fallback", 431 initial: "container=lxc", 432 final: "container=lxc", 433 }, { 434 desc: "container from fallback", 435 fallbacks: "container=lxc", 436 final: "container=lxc", 437 }, { 438 desc: "arch with empty fallback", 439 initial: "arch=amd64", 440 final: "arch=amd64", 441 }, { 442 desc: "arch with ignored fallback", 443 initial: "arch=amd64", 444 fallbacks: "arch=i386", 445 final: "arch=amd64", 446 }, { 447 desc: "arch from fallback", 448 fallbacks: "arch=i386", 449 final: "arch=i386", 450 }, { 451 desc: "cpu-cores with empty fallback", 452 initial: "cpu-cores=2", 453 final: "cpu-cores=2", 454 }, { 455 desc: "cpu-cores with ignored fallback", 456 initial: "cpu-cores=4", 457 fallbacks: "cpu-cores=8", 458 final: "cpu-cores=4", 459 }, { 460 desc: "cpu-cores from fallback", 461 fallbacks: "cpu-cores=8", 462 final: "cpu-cores=8", 463 }, { 464 desc: "cpu-power with empty fallback", 465 initial: "cpu-power=100", 466 final: "cpu-power=100", 467 }, { 468 desc: "cpu-power with ignored fallback", 469 initial: "cpu-power=100", 470 fallbacks: "cpu-power=200", 471 final: "cpu-power=100", 472 }, { 473 desc: "cpu-power from fallback", 474 fallbacks: "cpu-power=200", 475 final: "cpu-power=200", 476 }, { 477 desc: "tags with empty fallback", 478 initial: "tags=foo,bar", 479 final: "tags=foo,bar", 480 }, { 481 desc: "tags with ignored fallback", 482 initial: "tags=foo,bar", 483 fallbacks: "tags=baz", 484 final: "tags=foo,bar", 485 }, { 486 desc: "tags from fallback", 487 fallbacks: "tags=foo,bar", 488 final: "tags=foo,bar", 489 }, { 490 desc: "tags inital empty", 491 initial: "tags=", 492 fallbacks: "tags=foo,bar", 493 final: "tags=", 494 }, { 495 desc: "mem with empty fallback", 496 initial: "mem=4G", 497 final: "mem=4G", 498 }, { 499 desc: "mem with ignored fallback", 500 initial: "mem=4G", 501 fallbacks: "mem=8G", 502 final: "mem=4G", 503 }, { 504 desc: "mem from fallback", 505 fallbacks: "mem=8G", 506 final: "mem=8G", 507 }, { 508 desc: "root-disk with empty fallback", 509 initial: "root-disk=4G", 510 final: "root-disk=4G", 511 }, { 512 desc: "root-disk with ignored fallback", 513 initial: "root-disk=4G", 514 fallbacks: "root-disk=8G", 515 final: "root-disk=4G", 516 }, { 517 desc: "root-disk from fallback", 518 fallbacks: "root-disk=8G", 519 final: "root-disk=8G", 520 }, { 521 desc: "non-overlapping mix", 522 initial: "root-disk=8G mem=4G arch=amd64", 523 fallbacks: "cpu-power=1000 cpu-cores=4", 524 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", 525 }, { 526 desc: "overlapping mix", 527 initial: "root-disk=8G mem=4G arch=amd64", 528 fallbacks: "cpu-power=1000 cpu-cores=4 mem=8G", 529 final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", 530 }, 531 } 532 533 func (s *ConstraintsSuite) TestWithFallbacks(c *gc.C) { 534 for i, t := range withFallbacksTests { 535 c.Logf("test %d: %s", i, t.desc) 536 initial := constraints.MustParse(t.initial) 537 fallbacks := constraints.MustParse(t.fallbacks) 538 final := constraints.MustParse(t.final) 539 c.Check(initial.WithFallbacks(fallbacks), gc.DeepEquals, final) 540 } 541 } 542 543 var hasContainerTests = []struct { 544 constraints string 545 hasContainer bool 546 }{ 547 { 548 hasContainer: false, 549 }, { 550 constraints: "container=lxc", 551 hasContainer: true, 552 }, { 553 constraints: "container=none", 554 hasContainer: false, 555 }, 556 } 557 558 func (s *ConstraintsSuite) TestHasContainer(c *gc.C) { 559 for i, t := range hasContainerTests { 560 c.Logf("test %d", i) 561 cons := constraints.MustParse(t.constraints) 562 c.Check(cons.HasContainer(), gc.Equals, t.hasContainer) 563 } 564 }