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  }