github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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  	"fmt"
     9  	"strings"
    10  	"testing"
    11  
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  	goyaml "gopkg.in/yaml.v2"
    15  
    16  	"github.com/juju/juju/constraints"
    17  	"github.com/juju/juju/instance"
    18  )
    19  
    20  func TestPackage(t *testing.T) {
    21  	gc.TestingT(t)
    22  }
    23  
    24  type ConstraintsSuite struct{}
    25  
    26  var _ = gc.Suite(&ConstraintsSuite{})
    27  
    28  var parseConstraintsTests = []struct {
    29  	summary string
    30  	args    []string
    31  	err     string
    32  }{
    33  	// Simple errors.
    34  	{
    35  		summary: "nothing at all",
    36  	}, {
    37  		summary: "empty",
    38  		args:    []string{"     "},
    39  	}, {
    40  		summary: "complete nonsense",
    41  		args:    []string{"cheese"},
    42  		err:     `malformed constraint "cheese"`,
    43  	}, {
    44  		summary: "missing name",
    45  		args:    []string{"=cheese"},
    46  		err:     `malformed constraint "=cheese"`,
    47  	}, {
    48  		summary: "unknown constraint",
    49  		args:    []string{"cheese=edam"},
    50  		err:     `unknown constraint "cheese"`,
    51  	},
    52  
    53  	// "container" in detail.
    54  	{
    55  		summary: "set container empty",
    56  		args:    []string{"container="},
    57  	}, {
    58  		summary: "set container to none",
    59  		args:    []string{"container=none"},
    60  	}, {
    61  		summary: "set container lxc",
    62  		args:    []string{"container=lxc"},
    63  	}, {
    64  		summary: "set nonsense container",
    65  		args:    []string{"container=foo"},
    66  		err:     `bad "container" constraint: invalid container type "foo"`,
    67  	}, {
    68  		summary: "double set container together",
    69  		args:    []string{"container=lxc container=lxc"},
    70  		err:     `bad "container" constraint: already set`,
    71  	}, {
    72  		summary: "double set container separately",
    73  		args:    []string{"container=lxc", "container="},
    74  		err:     `bad "container" constraint: already set`,
    75  	},
    76  
    77  	// "arch" in detail.
    78  	{
    79  		summary: "set arch empty",
    80  		args:    []string{"arch="},
    81  	}, {
    82  		summary: "set arch amd64",
    83  		args:    []string{"arch=amd64"},
    84  	}, {
    85  		summary: "set arch i386",
    86  		args:    []string{"arch=i386"},
    87  	}, {
    88  		summary: "set arch armhf",
    89  		args:    []string{"arch=armhf"},
    90  	}, {
    91  		summary: "set nonsense arch 1",
    92  		args:    []string{"arch=cheese"},
    93  		err:     `bad "arch" constraint: "cheese" not recognized`,
    94  	}, {
    95  		summary: "set nonsense arch 2",
    96  		args:    []string{"arch=123.45"},
    97  		err:     `bad "arch" constraint: "123.45" not recognized`,
    98  	}, {
    99  		summary: "double set arch together",
   100  		args:    []string{"arch=amd64 arch=amd64"},
   101  		err:     `bad "arch" constraint: already set`,
   102  	}, {
   103  		summary: "double set arch separately",
   104  		args:    []string{"arch=armhf", "arch="},
   105  		err:     `bad "arch" constraint: already set`,
   106  	},
   107  
   108  	// "cpu-cores" in detail.
   109  	{
   110  		summary: "set cpu-cores empty",
   111  		args:    []string{"cpu-cores="},
   112  	}, {
   113  		summary: "set cpu-cores zero",
   114  		args:    []string{"cpu-cores=0"},
   115  	}, {
   116  		summary: "set cpu-cores",
   117  		args:    []string{"cpu-cores=4"},
   118  	}, {
   119  		summary: "set nonsense cpu-cores 1",
   120  		args:    []string{"cpu-cores=cheese"},
   121  		err:     `bad "cpu-cores" constraint: must be a non-negative integer`,
   122  	}, {
   123  		summary: "set nonsense cpu-cores 2",
   124  		args:    []string{"cpu-cores=-1"},
   125  		err:     `bad "cpu-cores" constraint: must be a non-negative integer`,
   126  	}, {
   127  		summary: "set nonsense cpu-cores 3",
   128  		args:    []string{"cpu-cores=123.45"},
   129  		err:     `bad "cpu-cores" constraint: must be a non-negative integer`,
   130  	}, {
   131  		summary: "double set cpu-cores together",
   132  		args:    []string{"cpu-cores=128 cpu-cores=1"},
   133  		err:     `bad "cpu-cores" constraint: already set`,
   134  	}, {
   135  		summary: "double set cpu-cores separately",
   136  		args:    []string{"cpu-cores=128", "cpu-cores=1"},
   137  		err:     `bad "cpu-cores" constraint: already set`,
   138  	},
   139  
   140  	// "cpu-power" in detail.
   141  	{
   142  		summary: "set cpu-power empty",
   143  		args:    []string{"cpu-power="},
   144  	}, {
   145  		summary: "set cpu-power zero",
   146  		args:    []string{"cpu-power=0"},
   147  	}, {
   148  		summary: "set cpu-power",
   149  		args:    []string{"cpu-power=44"},
   150  	}, {
   151  		summary: "set nonsense cpu-power 1",
   152  		args:    []string{"cpu-power=cheese"},
   153  		err:     `bad "cpu-power" constraint: must be a non-negative integer`,
   154  	}, {
   155  		summary: "set nonsense cpu-power 2",
   156  		args:    []string{"cpu-power=-1"},
   157  		err:     `bad "cpu-power" constraint: must be a non-negative integer`,
   158  	}, {
   159  		summary: "double set cpu-power together",
   160  		args:    []string{"  cpu-power=300 cpu-power=1700 "},
   161  		err:     `bad "cpu-power" constraint: already set`,
   162  	}, {
   163  		summary: "double set cpu-power separately",
   164  		args:    []string{"cpu-power=300  ", "  cpu-power=1700"},
   165  		err:     `bad "cpu-power" constraint: already set`,
   166  	},
   167  
   168  	// "mem" in detail.
   169  	{
   170  		summary: "set mem empty",
   171  		args:    []string{"mem="},
   172  	}, {
   173  		summary: "set mem zero",
   174  		args:    []string{"mem=0"},
   175  	}, {
   176  		summary: "set mem without suffix",
   177  		args:    []string{"mem=512"},
   178  	}, {
   179  		summary: "set mem with M suffix",
   180  		args:    []string{"mem=512M"},
   181  	}, {
   182  		summary: "set mem with G suffix",
   183  		args:    []string{"mem=1.5G"},
   184  	}, {
   185  		summary: "set mem with T suffix",
   186  		args:    []string{"mem=36.2T"},
   187  	}, {
   188  		summary: "set mem with P suffix",
   189  		args:    []string{"mem=18.9P"},
   190  	}, {
   191  		summary: "set nonsense mem 1",
   192  		args:    []string{"mem=cheese"},
   193  		err:     `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   194  	}, {
   195  		summary: "set nonsense mem 2",
   196  		args:    []string{"mem=-1"},
   197  		err:     `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   198  	}, {
   199  		summary: "set nonsense mem 3",
   200  		args:    []string{"mem=32Y"},
   201  		err:     `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   202  	}, {
   203  		summary: "double set mem together",
   204  		args:    []string{"mem=1G  mem=2G"},
   205  		err:     `bad "mem" constraint: already set`,
   206  	}, {
   207  		summary: "double set mem separately",
   208  		args:    []string{"mem=1G", "mem=2G"},
   209  		err:     `bad "mem" constraint: already set`,
   210  	},
   211  
   212  	// "root-disk" in detail.
   213  	{
   214  		summary: "set root-disk empty",
   215  		args:    []string{"root-disk="},
   216  	}, {
   217  		summary: "set root-disk zero",
   218  		args:    []string{"root-disk=0"},
   219  	}, {
   220  		summary: "set root-disk without suffix",
   221  		args:    []string{"root-disk=512"},
   222  	}, {
   223  		summary: "set root-disk with M suffix",
   224  		args:    []string{"root-disk=512M"},
   225  	}, {
   226  		summary: "set root-disk with G suffix",
   227  		args:    []string{"root-disk=1.5G"},
   228  	}, {
   229  		summary: "set root-disk with T suffix",
   230  		args:    []string{"root-disk=36.2T"},
   231  	}, {
   232  		summary: "set root-disk with P suffix",
   233  		args:    []string{"root-disk=18.9P"},
   234  	}, {
   235  		summary: "set nonsense root-disk 1",
   236  		args:    []string{"root-disk=cheese"},
   237  		err:     `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   238  	}, {
   239  		summary: "set nonsense root-disk 2",
   240  		args:    []string{"root-disk=-1"},
   241  		err:     `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   242  	}, {
   243  		summary: "set nonsense root-disk 3",
   244  		args:    []string{"root-disk=32Y"},
   245  		err:     `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   246  	}, {
   247  		summary: "double set root-disk together",
   248  		args:    []string{"root-disk=1G  root-disk=2G"},
   249  		err:     `bad "root-disk" constraint: already set`,
   250  	}, {
   251  		summary: "double set root-disk separately",
   252  		args:    []string{"root-disk=1G", "root-disk=2G"},
   253  		err:     `bad "root-disk" constraint: already set`,
   254  	},
   255  
   256  	// tags
   257  	{
   258  		summary: "single tag",
   259  		args:    []string{"tags=foo"},
   260  	}, {
   261  		summary: "multiple tags",
   262  		args:    []string{"tags=foo,bar"},
   263  	}, {
   264  		summary: "no tags",
   265  		args:    []string{"tags="},
   266  	},
   267  
   268  	// spaces
   269  	{
   270  		summary: "single space",
   271  		args:    []string{"spaces=space1"},
   272  	}, {
   273  		summary: "multiple spaces - positive",
   274  		args:    []string{"spaces=space1,space2"},
   275  	}, {
   276  		summary: "multiple spaces - negative",
   277  		args:    []string{"spaces=^dmz,^public"},
   278  	}, {
   279  		summary: "multiple spaces - positive and negative",
   280  		args:    []string{"spaces=admin,^area52,dmz,^public"},
   281  	}, {
   282  		summary: "no spaces",
   283  		args:    []string{"spaces="},
   284  	},
   285  
   286  	// instance type
   287  	{
   288  		summary: "set instance type",
   289  		args:    []string{"instance-type=foo"},
   290  	}, {
   291  		summary: "instance type empty",
   292  		args:    []string{"instance-type="},
   293  	},
   294  
   295  	// "virt-type" in detail.
   296  	{
   297  		summary: "set virt-type empty",
   298  		args:    []string{"virt-type="},
   299  	}, {
   300  		summary: "set virt-type kvm",
   301  		args:    []string{"virt-type=kvm"},
   302  	}, {
   303  		summary: "set virt-type lxd",
   304  		args:    []string{"virt-type=lxd"},
   305  	}, {
   306  		summary: "double set virt-type together",
   307  		args:    []string{"virt-type=kvm virt-type=kvm"},
   308  		err:     `bad "virt-type" constraint: already set`,
   309  	}, {
   310  		summary: "double set virt-type separately",
   311  		args:    []string{"virt-type=kvm", "virt-type="},
   312  		err:     `bad "virt-type" constraint: already set`,
   313  	},
   314  
   315  	// Everything at once.
   316  	{
   317  		summary: "kitchen sink together",
   318  		args: []string{
   319  			"root-disk=8G mem=2T  arch=i386  cpu-cores=4096 cpu-power=9001 container=lxc " +
   320  				"tags=foo,bar spaces=space1,^space2 instance-type=foo",
   321  			"virt-type=kvm"},
   322  	}, {
   323  		summary: "kitchen sink separately",
   324  		args: []string{
   325  			"root-disk=8G", "mem=2T", "cpu-cores=4096", "cpu-power=9001", "arch=armhf",
   326  			"container=lxc", "tags=foo,bar", "spaces=space1,^space2",
   327  			"instance-type=foo", "virt-type=kvm"},
   328  	},
   329  }
   330  
   331  func (s *ConstraintsSuite) TestParseConstraints(c *gc.C) {
   332  	// TODO(dimitern): This test is inadequate and needs to check for
   333  	// more than just the reparsed output of String() matches the
   334  	// expected.
   335  	for i, t := range parseConstraintsTests {
   336  		c.Logf("test %d: %s", i, t.summary)
   337  		cons0, err := constraints.Parse(t.args...)
   338  		if t.err == "" {
   339  			c.Assert(err, jc.ErrorIsNil)
   340  		} else {
   341  			c.Assert(err, gc.ErrorMatches, t.err)
   342  			continue
   343  		}
   344  		cons1, err := constraints.Parse(cons0.String())
   345  		c.Check(err, jc.ErrorIsNil)
   346  		c.Check(cons1, gc.DeepEquals, cons0)
   347  	}
   348  }
   349  
   350  func (s *ConstraintsSuite) TestMerge(c *gc.C) {
   351  	con1 := constraints.MustParse("arch=amd64 mem=4G")
   352  	con2 := constraints.MustParse("cpu-cores=42")
   353  	con3 := constraints.MustParse(
   354  		"root-disk=8G container=lxc spaces=space1,^space2",
   355  	)
   356  	merged, err := constraints.Merge(con1, con2)
   357  	c.Assert(err, jc.ErrorIsNil)
   358  	c.Assert(merged, jc.DeepEquals, constraints.MustParse("arch=amd64 mem=4G cpu-cores=42"))
   359  	merged, err = constraints.Merge(con1)
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	c.Assert(merged, jc.DeepEquals, con1)
   362  	merged, err = constraints.Merge(con1, con2, con3)
   363  	c.Assert(err, jc.ErrorIsNil)
   364  	c.Assert(merged, jc.DeepEquals, constraints.
   365  		MustParse("arch=amd64 mem=4G cpu-cores=42 root-disk=8G container=lxc spaces=space1,^space2"),
   366  	)
   367  	merged, err = constraints.Merge()
   368  	c.Assert(err, jc.ErrorIsNil)
   369  	c.Assert(merged, jc.DeepEquals, constraints.Value{})
   370  	foo := "foo"
   371  	merged, err = constraints.Merge(constraints.Value{Arch: &foo}, con2)
   372  	c.Assert(err, gc.NotNil)
   373  	c.Assert(err, gc.ErrorMatches, `bad "arch" constraint: "foo" not recognized`)
   374  	c.Assert(merged, jc.DeepEquals, constraints.Value{})
   375  	merged, err = constraints.Merge(con1, con1)
   376  	c.Assert(err, gc.NotNil)
   377  	c.Assert(err, gc.ErrorMatches, `bad "arch" constraint: already set`)
   378  	c.Assert(merged, jc.DeepEquals, constraints.Value{})
   379  }
   380  
   381  func (s *ConstraintsSuite) TestParseMissingTagsAndSpaces(c *gc.C) {
   382  	con := constraints.MustParse("arch=amd64 mem=4G cpu-cores=1 root-disk=8G")
   383  	c.Check(con.Tags, gc.IsNil)
   384  	c.Check(con.Spaces, gc.IsNil)
   385  }
   386  
   387  func (s *ConstraintsSuite) TestParseNoTagsNoSpaces(c *gc.C) {
   388  	con := constraints.MustParse(
   389  		"arch=amd64 mem=4G cpu-cores=1 root-disk=8G tags= spaces=",
   390  	)
   391  	c.Assert(con.Tags, gc.Not(gc.IsNil))
   392  	c.Assert(con.Spaces, gc.Not(gc.IsNil))
   393  	c.Check(*con.Tags, gc.HasLen, 0)
   394  	c.Check(*con.Spaces, gc.HasLen, 0)
   395  }
   396  
   397  func (s *ConstraintsSuite) TestIncludeExcludeAndHaveSpaces(c *gc.C) {
   398  	con := constraints.MustParse("spaces=space1,^space2,space3,^space4")
   399  	c.Assert(con.Spaces, gc.Not(gc.IsNil))
   400  	c.Check(*con.Spaces, gc.HasLen, 4)
   401  	c.Check(con.IncludeSpaces(), jc.SameContents, []string{"space1", "space3"})
   402  	c.Check(con.ExcludeSpaces(), jc.SameContents, []string{"space2", "space4"})
   403  	c.Check(con.HaveSpaces(), jc.IsTrue)
   404  	con = constraints.MustParse("mem=4G")
   405  	c.Check(con.HaveSpaces(), jc.IsFalse)
   406  	con = constraints.MustParse("mem=4G spaces=space-foo,^space-bar")
   407  	c.Check(con.IncludeSpaces(), jc.SameContents, []string{"space-foo"})
   408  	c.Check(con.ExcludeSpaces(), jc.SameContents, []string{"space-bar"})
   409  	c.Check(con.HaveSpaces(), jc.IsTrue)
   410  }
   411  
   412  func (s *ConstraintsSuite) TestInvalidSpaces(c *gc.C) {
   413  	invalidNames := []string{
   414  		"%$pace", "^foo#2", "+", "tcp:ip",
   415  		"^^myspace", "^^^^^^^^", "space^x",
   416  		"&-foo", "space/3", "^bar=4", "&#!",
   417  	}
   418  	for _, name := range invalidNames {
   419  		con, err := constraints.Parse("spaces=" + name)
   420  		expectName := strings.TrimPrefix(name, "^")
   421  		expectErr := fmt.Sprintf(`bad "spaces" constraint: %q is not a valid space name`, expectName)
   422  		c.Check(err, gc.NotNil)
   423  		c.Check(err.Error(), gc.Equals, expectErr)
   424  		c.Check(con, jc.DeepEquals, constraints.Value{})
   425  	}
   426  }
   427  
   428  func (s *ConstraintsSuite) TestIsEmpty(c *gc.C) {
   429  	con := constraints.Value{}
   430  	c.Check(&con, jc.Satisfies, constraints.IsEmpty)
   431  	con = constraints.MustParse("arch=amd64")
   432  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   433  	con = constraints.MustParse("")
   434  	c.Check(&con, jc.Satisfies, constraints.IsEmpty)
   435  	con = constraints.MustParse("tags=")
   436  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   437  	con = constraints.MustParse("spaces=")
   438  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   439  	con = constraints.MustParse("mem=")
   440  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   441  	con = constraints.MustParse("arch=")
   442  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   443  	con = constraints.MustParse("root-disk=")
   444  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   445  	con = constraints.MustParse("cpu-power=")
   446  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   447  	con = constraints.MustParse("cpu-cores=")
   448  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   449  	con = constraints.MustParse("container=")
   450  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   451  	con = constraints.MustParse("instance-type=")
   452  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   453  }
   454  
   455  func uint64p(i uint64) *uint64 {
   456  	return &i
   457  }
   458  
   459  func strp(s string) *string {
   460  	return &s
   461  }
   462  
   463  func ctypep(ctype string) *instance.ContainerType {
   464  	res := instance.ContainerType(ctype)
   465  	return &res
   466  }
   467  
   468  type roundTrip struct {
   469  	Name  string
   470  	Value constraints.Value
   471  }
   472  
   473  var constraintsRoundtripTests = []roundTrip{
   474  	{"empty", constraints.Value{}},
   475  	{"Arch1", constraints.Value{Arch: strp("")}},
   476  	{"Arch2", constraints.Value{Arch: strp("amd64")}},
   477  	{"Container1", constraints.Value{Container: ctypep("")}},
   478  	{"Container2", constraints.Value{Container: ctypep("lxc")}},
   479  	{"Container3", constraints.Value{Container: nil}},
   480  	{"CpuCores1", constraints.Value{CpuCores: nil}},
   481  	{"CpuCores2", constraints.Value{CpuCores: uint64p(0)}},
   482  	{"CpuCores3", constraints.Value{CpuCores: uint64p(128)}},
   483  	{"CpuPower1", constraints.Value{CpuPower: nil}},
   484  	{"CpuPower2", constraints.Value{CpuPower: uint64p(0)}},
   485  	{"CpuPower3", constraints.Value{CpuPower: uint64p(250)}},
   486  	{"Mem1", constraints.Value{Mem: nil}},
   487  	{"Mem2", constraints.Value{Mem: uint64p(0)}},
   488  	{"Mem3", constraints.Value{Mem: uint64p(98765)}},
   489  	{"RootDisk1", constraints.Value{RootDisk: nil}},
   490  	{"RootDisk2", constraints.Value{RootDisk: uint64p(0)}},
   491  	{"RootDisk2", constraints.Value{RootDisk: uint64p(109876)}},
   492  	{"Tags1", constraints.Value{Tags: nil}},
   493  	{"Tags2", constraints.Value{Tags: &[]string{}}},
   494  	{"Tags3", constraints.Value{Tags: &[]string{"foo", "bar"}}},
   495  	{"Spaces1", constraints.Value{Spaces: nil}},
   496  	{"Spaces2", constraints.Value{Spaces: &[]string{}}},
   497  	{"Spaces3", constraints.Value{Spaces: &[]string{"space1", "^space2"}}},
   498  	{"InstanceType1", constraints.Value{InstanceType: strp("")}},
   499  	{"InstanceType2", constraints.Value{InstanceType: strp("foo")}},
   500  	{"All", constraints.Value{
   501  		Arch:         strp("i386"),
   502  		Container:    ctypep("lxc"),
   503  		CpuCores:     uint64p(4096),
   504  		CpuPower:     uint64p(9001),
   505  		Mem:          uint64p(18000000000),
   506  		RootDisk:     uint64p(24000000000),
   507  		Tags:         &[]string{"foo", "bar"},
   508  		Spaces:       &[]string{"space1", "^space2"},
   509  		InstanceType: strp("foo"),
   510  	}},
   511  }
   512  
   513  func (s *ConstraintsSuite) TestRoundtripGnuflagValue(c *gc.C) {
   514  	for _, t := range constraintsRoundtripTests {
   515  		c.Logf("test %s", t.Name)
   516  		var cons constraints.Value
   517  		val := constraints.ConstraintsValue{&cons}
   518  		err := val.Set(t.Value.String())
   519  		c.Check(err, jc.ErrorIsNil)
   520  		c.Check(cons, jc.DeepEquals, t.Value)
   521  	}
   522  }
   523  
   524  func (s *ConstraintsSuite) TestRoundtripString(c *gc.C) {
   525  	for _, t := range constraintsRoundtripTests {
   526  		c.Logf("test %s", t.Name)
   527  		cons, err := constraints.Parse(t.Value.String())
   528  		c.Check(err, jc.ErrorIsNil)
   529  		c.Check(cons, jc.DeepEquals, t.Value)
   530  	}
   531  }
   532  
   533  func (s *ConstraintsSuite) TestRoundtripJson(c *gc.C) {
   534  	for _, t := range constraintsRoundtripTests {
   535  		c.Logf("test %s", t.Name)
   536  		data, err := json.Marshal(t.Value)
   537  		c.Assert(err, jc.ErrorIsNil)
   538  		var cons constraints.Value
   539  		err = json.Unmarshal(data, &cons)
   540  		c.Check(err, jc.ErrorIsNil)
   541  		c.Check(cons, jc.DeepEquals, t.Value)
   542  	}
   543  }
   544  
   545  func (s *ConstraintsSuite) TestRoundtripYaml(c *gc.C) {
   546  	for _, t := range constraintsRoundtripTests {
   547  		c.Logf("test %s", t.Name)
   548  		data, err := goyaml.Marshal(t.Value)
   549  		c.Assert(err, jc.ErrorIsNil)
   550  		var cons constraints.Value
   551  		err = goyaml.Unmarshal(data, &cons)
   552  		c.Check(err, jc.ErrorIsNil)
   553  		c.Check(cons, jc.DeepEquals, t.Value)
   554  	}
   555  }
   556  
   557  var hasContainerTests = []struct {
   558  	constraints  string
   559  	hasContainer bool
   560  }{
   561  	{
   562  		hasContainer: false,
   563  	}, {
   564  		constraints:  "container=lxc",
   565  		hasContainer: true,
   566  	}, {
   567  		constraints:  "container=none",
   568  		hasContainer: false,
   569  	},
   570  }
   571  
   572  func (s *ConstraintsSuite) TestHasContainer(c *gc.C) {
   573  	for i, t := range hasContainerTests {
   574  		c.Logf("test %d", i)
   575  		cons := constraints.MustParse(t.constraints)
   576  		c.Check(cons.HasContainer(), gc.Equals, t.hasContainer)
   577  	}
   578  }
   579  
   580  func (s *ConstraintsSuite) TestHasInstanceType(c *gc.C) {
   581  	cons := constraints.MustParse("arch=amd64")
   582  	c.Check(cons.HasInstanceType(), jc.IsFalse)
   583  	cons = constraints.MustParse("arch=amd64 instance-type=foo")
   584  	c.Check(cons.HasInstanceType(), jc.IsTrue)
   585  }
   586  
   587  const initialWithoutCons = "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 spaces=space1,^space2 tags=foo container=lxc instance-type=bar"
   588  
   589  var withoutTests = []struct {
   590  	initial string
   591  	without []string
   592  	final   string
   593  }{{
   594  	initial: initialWithoutCons,
   595  	without: []string{"root-disk"},
   596  	final:   "mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar",
   597  }, {
   598  	initial: initialWithoutCons,
   599  	without: []string{"mem"},
   600  	final:   "root-disk=8G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar",
   601  }, {
   602  	initial: initialWithoutCons,
   603  	without: []string{"arch"},
   604  	final:   "root-disk=8G mem=4G cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar",
   605  }, {
   606  	initial: initialWithoutCons,
   607  	without: []string{"cpu-power"},
   608  	final:   "root-disk=8G mem=4G arch=amd64 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar",
   609  }, {
   610  	initial: initialWithoutCons,
   611  	without: []string{"cpu-cores"},
   612  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 tags=foo spaces=space1,^space2 container=lxc instance-type=bar",
   613  }, {
   614  	initial: initialWithoutCons,
   615  	without: []string{"tags"},
   616  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 spaces=space1,^space2 container=lxc instance-type=bar",
   617  }, {
   618  	initial: initialWithoutCons,
   619  	without: []string{"spaces"},
   620  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar",
   621  }, {
   622  	initial: initialWithoutCons,
   623  	without: []string{"container"},
   624  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 instance-type=bar",
   625  }, {
   626  	initial: initialWithoutCons,
   627  	without: []string{"instance-type"},
   628  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc",
   629  }, {
   630  	initial: initialWithoutCons,
   631  	without: []string{"root-disk", "mem", "arch"},
   632  	final:   "cpu-power=1000 cpu-cores=4 tags=foo spaces=space1,^space2 container=lxc instance-type=bar",
   633  }}
   634  
   635  func (s *ConstraintsSuite) TestWithout(c *gc.C) {
   636  	for i, t := range withoutTests {
   637  		c.Logf("test %d", i)
   638  		initial := constraints.MustParse(t.initial)
   639  		final, err := constraints.Without(initial, t.without...)
   640  		c.Assert(err, jc.ErrorIsNil)
   641  		c.Check(final, jc.DeepEquals, constraints.MustParse(t.final))
   642  	}
   643  }
   644  
   645  func (s *ConstraintsSuite) TestWithoutError(c *gc.C) {
   646  	cons := constraints.MustParse("mem=4G")
   647  	_, err := constraints.Without(cons, "foo")
   648  	c.Assert(err, gc.ErrorMatches, `unknown constraint "foo"`)
   649  }
   650  
   651  func (s *ConstraintsSuite) TestAttributesWithValues(c *gc.C) {
   652  	for i, consStr := range []string{
   653  		"",
   654  		"root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 instance-type=foo tags=foo,bar spaces=space1,^space2",
   655  	} {
   656  		c.Logf("test %d", i)
   657  		cons := constraints.MustParse(consStr)
   658  		obtained := constraints.AttributesWithValues(cons)
   659  		assertMissing := func(attrName string) {
   660  			_, ok := obtained[attrName]
   661  			c.Check(ok, jc.IsFalse)
   662  		}
   663  		if cons.Arch != nil {
   664  			c.Check(obtained["arch"], gc.Equals, *cons.Arch)
   665  		} else {
   666  			assertMissing("arch")
   667  		}
   668  		if cons.Mem != nil {
   669  			c.Check(obtained["mem"], gc.Equals, *cons.Mem)
   670  		} else {
   671  			assertMissing("mem")
   672  		}
   673  		if cons.CpuCores != nil {
   674  			c.Check(obtained["cpu-cores"], gc.Equals, *cons.CpuCores)
   675  		} else {
   676  			assertMissing("cpu-cores")
   677  		}
   678  		if cons.CpuPower != nil {
   679  			c.Check(obtained["cpu-power"], gc.Equals, *cons.CpuPower)
   680  		} else {
   681  			assertMissing("cpu-power")
   682  		}
   683  		if cons.RootDisk != nil {
   684  			c.Check(obtained["root-disk"], gc.Equals, *cons.RootDisk)
   685  		} else {
   686  			assertMissing("root-disk")
   687  		}
   688  		if cons.Tags != nil {
   689  			c.Check(obtained["tags"], gc.DeepEquals, *cons.Tags)
   690  		} else {
   691  			assertMissing("tags")
   692  		}
   693  		if cons.Spaces != nil {
   694  			c.Check(obtained["spaces"], gc.DeepEquals, *cons.Spaces)
   695  		} else {
   696  			assertMissing("spaces")
   697  		}
   698  		if cons.InstanceType != nil {
   699  			c.Check(obtained["instance-type"], gc.Equals, *cons.InstanceType)
   700  		} else {
   701  			assertMissing("instance-type")
   702  		}
   703  	}
   704  }
   705  
   706  var hasAnyTests = []struct {
   707  	cons     string
   708  	attrs    []string
   709  	expected []string
   710  }{
   711  	{
   712  		cons:     "root-disk=8G mem=4G arch=amd64 cpu-power=1000 spaces=space1,^space2 cpu-cores=4",
   713  		attrs:    []string{"root-disk", "tags", "mem", "spaces"},
   714  		expected: []string{"root-disk", "mem", "spaces"},
   715  	},
   716  	{
   717  		cons:     "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4",
   718  		attrs:    []string{"root-disk", "tags", "mem"},
   719  		expected: []string{"root-disk", "mem"},
   720  	},
   721  	{
   722  		cons:     "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4",
   723  		attrs:    []string{"tags", "spaces"},
   724  		expected: []string{},
   725  	},
   726  }
   727  
   728  func (s *ConstraintsSuite) TestHasAny(c *gc.C) {
   729  	for i, t := range hasAnyTests {
   730  		c.Logf("test %d", i)
   731  		cons := constraints.MustParse(t.cons)
   732  		obtained := constraints.HasAny(cons, t.attrs...)
   733  		c.Check(obtained, jc.DeepEquals, t.expected)
   734  	}
   735  }