gopkg.in/juju/charm.v6-unstable@v6.0.0-20171026192109-50d0c219b496/meta_test.go (about)

     1  // Copyright 2011, 2012, 2013 Canonical Ltd.
     2  // Licensed under the LGPLv3, see LICENCE file for details.
     3  
     4  package charm_test
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	jc "github.com/juju/testing/checkers"
    16  	"github.com/juju/version"
    17  	gc "gopkg.in/check.v1"
    18  	"gopkg.in/yaml.v2"
    19  	yamlv2 "gopkg.in/yaml.v2"
    20  
    21  	"gopkg.in/juju/charm.v6-unstable"
    22  	"gopkg.in/juju/charm.v6-unstable/resource"
    23  )
    24  
    25  func repoMeta(c *gc.C, name string) io.Reader {
    26  	charmDir := charmDirPath(c, name)
    27  	file, err := os.Open(filepath.Join(charmDir, "metadata.yaml"))
    28  	c.Assert(err, gc.IsNil)
    29  	defer file.Close()
    30  	data, err := ioutil.ReadAll(file)
    31  	c.Assert(err, gc.IsNil)
    32  	return bytes.NewReader(data)
    33  }
    34  
    35  type MetaSuite struct{}
    36  
    37  var _ = gc.Suite(&MetaSuite{})
    38  
    39  func (s *MetaSuite) TestReadMetaVersion1(c *gc.C) {
    40  	meta, err := charm.ReadMeta(repoMeta(c, "dummy"))
    41  	c.Assert(err, gc.IsNil)
    42  	c.Assert(meta.Name, gc.Equals, "dummy")
    43  	c.Assert(meta.Summary, gc.Equals, "That's a dummy charm.")
    44  	c.Assert(meta.Description, gc.Equals,
    45  		"This is a longer description which\npotentially contains multiple lines.\n")
    46  	c.Assert(meta.Subordinate, gc.Equals, false)
    47  }
    48  
    49  func (s *MetaSuite) TestReadMetaVersion2(c *gc.C) {
    50  	// This checks that we can accept a charm with the
    51  	// obsolete "format" field, even though we ignore it.
    52  	meta, err := charm.ReadMeta(repoMeta(c, "format2"))
    53  	c.Assert(err, gc.IsNil)
    54  	c.Assert(meta.Name, gc.Equals, "format2")
    55  	c.Assert(meta.Categories, gc.HasLen, 0)
    56  	c.Assert(meta.Terms, gc.HasLen, 0)
    57  }
    58  
    59  func (s *MetaSuite) TestValidTermFormat(c *gc.C) {
    60  	valid := []string{
    61  		"foobar",
    62  		"foobar/27",
    63  		"foo/003",
    64  		"owner/foobar/27",
    65  		"owner/foobar",
    66  		"owner/foo-bar",
    67  		"own-er/foobar",
    68  		"ibm/j9-jvm/2",
    69  		"cs:foobar/27",
    70  		"cs:foobar",
    71  	}
    72  
    73  	invalid := []string{
    74  		"/",
    75  		"/1",
    76  		"//",
    77  		"//2",
    78  		"27",
    79  		"owner/foo/foobar",
    80  		"@les/term/1",
    81  		"own_er/foobar",
    82  	}
    83  
    84  	for i, s := range valid {
    85  		c.Logf("valid test %d: %s", i, s)
    86  		meta := charm.Meta{Terms: []string{s}}
    87  		err := meta.Check()
    88  		c.Check(err, jc.ErrorIsNil)
    89  	}
    90  
    91  	for i, s := range invalid {
    92  		c.Logf("invalid test %d: %s", i, s)
    93  		meta := charm.Meta{Terms: []string{s}}
    94  		err := meta.Check()
    95  		c.Check(err, gc.NotNil)
    96  	}
    97  }
    98  
    99  func (s *MetaSuite) TestTermStringRoundTrip(c *gc.C) {
   100  	terms := []string{
   101  		"foobar",
   102  		"foobar/27",
   103  		"owner/foobar/27",
   104  		"owner/foobar",
   105  		"owner/foo-bar",
   106  		"own-er/foobar",
   107  		"ibm/j9-jvm/2",
   108  		"cs:foobar/27",
   109  	}
   110  	for i, term := range terms {
   111  		c.Logf("test %d: %s", i, term)
   112  		id, err := charm.ParseTerm(term)
   113  		c.Check(err, gc.IsNil)
   114  		c.Check(id.String(), gc.Equals, term)
   115  	}
   116  }
   117  
   118  func (s *MetaSuite) TestCheckTerms(c *gc.C) {
   119  	tests := []struct {
   120  		about       string
   121  		terms       []string
   122  		expectError string
   123  	}{{
   124  		about: "valid terms",
   125  		terms: []string{"term/1", "term/2", "term-without-revision", "tt/2"},
   126  	}, {
   127  		about:       "revision not a number",
   128  		terms:       []string{"term/1", "term/a"},
   129  		expectError: `wrong term name format "a"`,
   130  	}, {
   131  		about:       "negative revision",
   132  		terms:       []string{"term/-1"},
   133  		expectError: "negative term revision",
   134  	}, {
   135  		about:       "wrong format",
   136  		terms:       []string{"term/1", "foobar/term/abc/1"},
   137  		expectError: `unknown term id format "foobar/term/abc/1"`,
   138  	}, {
   139  		about: "term with owner",
   140  		terms: []string{"term/1", "term/abc/1"},
   141  	}, {
   142  		about: "term with owner no rev",
   143  		terms: []string{"term/1", "term/abc"},
   144  	}, {
   145  		about:       "term may not contain spaces",
   146  		terms:       []string{"term/1", "term about a term"},
   147  		expectError: `wrong term name format "term about a term"`,
   148  	}, {
   149  		about:       "term name must start with lowercase letter",
   150  		terms:       []string{"Term/1"},
   151  		expectError: `wrong term name format "Term"`,
   152  	}, {
   153  		about:       "term name cannot contain capital letters",
   154  		terms:       []string{"owner/foO-Bar"},
   155  		expectError: `wrong term name format "foO-Bar"`,
   156  	}, {
   157  		about:       "term name cannot contain underscores, that's what dashes are for",
   158  		terms:       []string{"owner/foo_bar"},
   159  		expectError: `wrong term name format "foo_bar"`,
   160  	}, {
   161  		about:       "term name can't end with a dash",
   162  		terms:       []string{"o-/1"},
   163  		expectError: `wrong term name format "o-"`,
   164  	}, {
   165  		about:       "term name can't contain consecutive dashes",
   166  		terms:       []string{"o-oo--ooo---o/1"},
   167  		expectError: `wrong term name format "o-oo--ooo---o"`,
   168  	}, {
   169  		about:       "term name more than a single char",
   170  		terms:       []string{"z/1"},
   171  		expectError: `wrong term name format "z"`,
   172  	}, {
   173  		about:       "term name match the regexp",
   174  		terms:       []string{"term_123-23aAf/1"},
   175  		expectError: `wrong term name format "term_123-23aAf"`,
   176  	},
   177  	}
   178  	for i, test := range tests {
   179  		c.Logf("running test %v: %v", i, test.about)
   180  		meta := charm.Meta{Terms: test.terms}
   181  		err := meta.Check()
   182  		if test.expectError == "" {
   183  			c.Check(err, jc.ErrorIsNil)
   184  		} else {
   185  			c.Check(err, gc.ErrorMatches, test.expectError)
   186  		}
   187  	}
   188  }
   189  
   190  func (s *MetaSuite) TestParseTerms(c *gc.C) {
   191  	tests := []struct {
   192  		about       string
   193  		term        string
   194  		expectError string
   195  		expectTerm  charm.TermsId
   196  	}{{
   197  		about:      "valid term",
   198  		term:       "term/1",
   199  		expectTerm: charm.TermsId{"", "", "term", 1},
   200  	}, {
   201  		about:      "valid term no revision",
   202  		term:       "term",
   203  		expectTerm: charm.TermsId{"", "", "term", 0},
   204  	}, {
   205  		about:       "revision not a number",
   206  		term:        "term/a",
   207  		expectError: `wrong term name format "a"`,
   208  	}, {
   209  		about:       "negative revision",
   210  		term:        "term/-1",
   211  		expectError: "negative term revision",
   212  	}, {
   213  		about:       "bad revision",
   214  		term:        "owner/term/12a",
   215  		expectError: `invalid revision number "12a" strconv.Atoi: parsing "12a": invalid syntax`,
   216  	}, {
   217  		about:       "wrong format",
   218  		term:        "foobar/term/abc/1",
   219  		expectError: `unknown term id format "foobar/term/abc/1"`,
   220  	}, {
   221  		about:      "term with owner",
   222  		term:       "term/abc/1",
   223  		expectTerm: charm.TermsId{"", "term", "abc", 1},
   224  	}, {
   225  		about:      "term with owner no rev",
   226  		term:       "term/abc",
   227  		expectTerm: charm.TermsId{"", "term", "abc", 0},
   228  	}, {
   229  		about:       "term may not contain spaces",
   230  		term:        "term about a term",
   231  		expectError: `wrong term name format "term about a term"`,
   232  	}, {
   233  		about:       "term name must not start with a number",
   234  		term:        "1Term/1",
   235  		expectError: `wrong term name format "1Term"`,
   236  	}, {
   237  		about:      "full term with tenant",
   238  		term:       "tenant:owner/term/1",
   239  		expectTerm: charm.TermsId{"tenant", "owner", "term", 1},
   240  	}, {
   241  		about:       "bad tenant",
   242  		term:        "tenant::owner/term/1",
   243  		expectError: `wrong owner format ":owner"`,
   244  	}, {
   245  		about:      "ownerless term with tenant",
   246  		term:       "tenant:term/1",
   247  		expectTerm: charm.TermsId{"tenant", "", "term", 1},
   248  	}, {
   249  		about:      "ownerless revisionless term with tenant",
   250  		term:       "tenant:term",
   251  		expectTerm: charm.TermsId{"tenant", "", "term", 0},
   252  	}, {
   253  		about:      "owner/term with tenant",
   254  		term:       "tenant:owner/term",
   255  		expectTerm: charm.TermsId{"tenant", "owner", "term", 0},
   256  	}, {
   257  		about:      "term with tenant",
   258  		term:       "tenant:term",
   259  		expectTerm: charm.TermsId{"tenant", "", "term", 0},
   260  	}}
   261  	for i, test := range tests {
   262  		c.Logf("running test %v: %v", i, test.about)
   263  		term, err := charm.ParseTerm(test.term)
   264  		if test.expectError == "" {
   265  			c.Check(err, jc.ErrorIsNil)
   266  			c.Check(term, gc.DeepEquals, &test.expectTerm)
   267  		} else {
   268  			c.Check(err, gc.ErrorMatches, test.expectError)
   269  			c.Check(term, gc.IsNil)
   270  		}
   271  	}
   272  }
   273  
   274  func (s *MetaSuite) TestReadCategory(c *gc.C) {
   275  	meta, err := charm.ReadMeta(repoMeta(c, "category"))
   276  	c.Assert(err, gc.IsNil)
   277  	c.Assert(meta.Categories, jc.DeepEquals, []string{"database"})
   278  }
   279  
   280  func (s *MetaSuite) TestReadTerms(c *gc.C) {
   281  	meta, err := charm.ReadMeta(repoMeta(c, "terms"))
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	err = meta.Check()
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	c.Assert(meta.Terms, jc.DeepEquals, []string{"term1/1", "term2", "owner/term3/1"})
   286  }
   287  
   288  var metaDataWithInvalidTermsId = `
   289  name: terms
   290  summary: "Sample charm with terms and conditions"
   291  description: |
   292          That's a boring charm that requires certain terms.
   293  terms: ["!!!/abc"]
   294  `
   295  
   296  func (s *MetaSuite) TestReadInvalidTerms(c *gc.C) {
   297  	reader := strings.NewReader(metaDataWithInvalidTermsId)
   298  	_, err := charm.ReadMeta(reader)
   299  	c.Assert(err, gc.ErrorMatches, `wrong owner format "!!!"`)
   300  }
   301  
   302  func (s *MetaSuite) TestReadTags(c *gc.C) {
   303  	meta, err := charm.ReadMeta(repoMeta(c, "category"))
   304  	c.Assert(err, gc.IsNil)
   305  	c.Assert(meta.Tags, jc.DeepEquals, []string{"openstack", "storage"})
   306  }
   307  
   308  func (s *MetaSuite) TestSubordinate(c *gc.C) {
   309  	meta, err := charm.ReadMeta(repoMeta(c, "logging"))
   310  	c.Assert(err, gc.IsNil)
   311  	c.Assert(meta.Subordinate, gc.Equals, true)
   312  }
   313  
   314  func (s *MetaSuite) TestSubordinateWithoutContainerRelation(c *gc.C) {
   315  	r := repoMeta(c, "dummy")
   316  	hackYaml := ReadYaml(r)
   317  	hackYaml["subordinate"] = true
   318  	_, err := charm.ReadMeta(hackYaml.Reader())
   319  	c.Assert(err, gc.ErrorMatches, "subordinate charm \"dummy\" lacks \"requires\" relation with container scope")
   320  }
   321  
   322  func (s *MetaSuite) TestScopeConstraint(c *gc.C) {
   323  	meta, err := charm.ReadMeta(repoMeta(c, "logging"))
   324  	c.Assert(err, gc.IsNil)
   325  	c.Assert(meta.Provides["logging-client"].Scope, gc.Equals, charm.ScopeGlobal)
   326  	c.Assert(meta.Requires["logging-directory"].Scope, gc.Equals, charm.ScopeContainer)
   327  	c.Assert(meta.Subordinate, gc.Equals, true)
   328  }
   329  
   330  func (s *MetaSuite) TestParseMetaRelations(c *gc.C) {
   331  	meta, err := charm.ReadMeta(repoMeta(c, "mysql"))
   332  	c.Assert(err, gc.IsNil)
   333  	c.Assert(meta.Provides["server"], gc.Equals, charm.Relation{
   334  		Name:      "server",
   335  		Role:      charm.RoleProvider,
   336  		Interface: "mysql",
   337  		Scope:     charm.ScopeGlobal,
   338  	})
   339  	c.Assert(meta.Requires, gc.IsNil)
   340  	c.Assert(meta.Peers, gc.IsNil)
   341  
   342  	meta, err = charm.ReadMeta(repoMeta(c, "riak"))
   343  	c.Assert(err, gc.IsNil)
   344  	c.Assert(meta.Provides["endpoint"], gc.Equals, charm.Relation{
   345  		Name:      "endpoint",
   346  		Role:      charm.RoleProvider,
   347  		Interface: "http",
   348  		Scope:     charm.ScopeGlobal,
   349  	})
   350  	c.Assert(meta.Provides["admin"], gc.Equals, charm.Relation{
   351  		Name:      "admin",
   352  		Role:      charm.RoleProvider,
   353  		Interface: "http",
   354  		Scope:     charm.ScopeGlobal,
   355  	})
   356  	c.Assert(meta.Peers["ring"], gc.Equals, charm.Relation{
   357  		Name:      "ring",
   358  		Role:      charm.RolePeer,
   359  		Interface: "riak",
   360  		Limit:     1,
   361  		Scope:     charm.ScopeGlobal,
   362  	})
   363  	c.Assert(meta.Requires, gc.IsNil)
   364  
   365  	meta, err = charm.ReadMeta(repoMeta(c, "terracotta"))
   366  	c.Assert(err, gc.IsNil)
   367  	c.Assert(meta.Provides["dso"], gc.Equals, charm.Relation{
   368  		Name:      "dso",
   369  		Role:      charm.RoleProvider,
   370  		Interface: "terracotta",
   371  		Optional:  true,
   372  		Scope:     charm.ScopeGlobal,
   373  	})
   374  	c.Assert(meta.Peers["server-array"], gc.Equals, charm.Relation{
   375  		Name:      "server-array",
   376  		Role:      charm.RolePeer,
   377  		Interface: "terracotta-server",
   378  		Limit:     1,
   379  		Scope:     charm.ScopeGlobal,
   380  	})
   381  	c.Assert(meta.Requires, gc.IsNil)
   382  
   383  	meta, err = charm.ReadMeta(repoMeta(c, "wordpress"))
   384  	c.Assert(err, gc.IsNil)
   385  	c.Assert(meta.Provides["url"], gc.Equals, charm.Relation{
   386  		Name:      "url",
   387  		Role:      charm.RoleProvider,
   388  		Interface: "http",
   389  		Scope:     charm.ScopeGlobal,
   390  	})
   391  	c.Assert(meta.Requires["db"], gc.Equals, charm.Relation{
   392  		Name:      "db",
   393  		Role:      charm.RoleRequirer,
   394  		Interface: "mysql",
   395  		Limit:     1,
   396  		Scope:     charm.ScopeGlobal,
   397  	})
   398  	c.Assert(meta.Requires["cache"], gc.Equals, charm.Relation{
   399  		Name:      "cache",
   400  		Role:      charm.RoleRequirer,
   401  		Interface: "varnish",
   402  		Limit:     2,
   403  		Optional:  true,
   404  		Scope:     charm.ScopeGlobal,
   405  	})
   406  	c.Assert(meta.Peers, gc.IsNil)
   407  }
   408  
   409  func (s *MetaSuite) TestCombinedRelations(c *gc.C) {
   410  	meta, err := charm.ReadMeta(repoMeta(c, "riak"))
   411  	c.Assert(err, gc.IsNil)
   412  	combinedRelations := meta.CombinedRelations()
   413  	expectedLength := len(meta.Provides) + len(meta.Requires) + len(meta.Peers)
   414  	c.Assert(combinedRelations, gc.HasLen, expectedLength)
   415  	c.Assert(combinedRelations, jc.DeepEquals, map[string]charm.Relation{
   416  		"endpoint": {
   417  			Name:      "endpoint",
   418  			Role:      charm.RoleProvider,
   419  			Interface: "http",
   420  			Scope:     charm.ScopeGlobal,
   421  		},
   422  		"admin": {
   423  			Name:      "admin",
   424  			Role:      charm.RoleProvider,
   425  			Interface: "http",
   426  			Scope:     charm.ScopeGlobal,
   427  		},
   428  		"ring": {
   429  			Name:      "ring",
   430  			Role:      charm.RolePeer,
   431  			Interface: "riak",
   432  			Limit:     1,
   433  			Scope:     charm.ScopeGlobal,
   434  		},
   435  	})
   436  }
   437  
   438  var relationsConstraintsTests = []struct {
   439  	rels string
   440  	err  string
   441  }{
   442  	{
   443  		"provides:\n  foo: ping\nrequires:\n  foo: pong",
   444  		`charm "a" using a duplicated relation name: "foo"`,
   445  	}, {
   446  		"requires:\n  foo: ping\npeers:\n  foo: pong",
   447  		`charm "a" using a duplicated relation name: "foo"`,
   448  	}, {
   449  		"peers:\n  foo: ping\nprovides:\n  foo: pong",
   450  		`charm "a" using a duplicated relation name: "foo"`,
   451  	}, {
   452  		"provides:\n  juju: blob",
   453  		`charm "a" using a reserved relation name: "juju"`,
   454  	}, {
   455  		"requires:\n  juju: blob",
   456  		`charm "a" using a reserved relation name: "juju"`,
   457  	}, {
   458  		"peers:\n  juju: blob",
   459  		`charm "a" using a reserved relation name: "juju"`,
   460  	}, {
   461  		"provides:\n  juju-snap: blub",
   462  		`charm "a" using a reserved relation name: "juju-snap"`,
   463  	}, {
   464  		"requires:\n  juju-crackle: blub",
   465  		`charm "a" using a reserved relation name: "juju-crackle"`,
   466  	}, {
   467  		"peers:\n  juju-pop: blub",
   468  		`charm "a" using a reserved relation name: "juju-pop"`,
   469  	}, {
   470  		"provides:\n  innocuous: juju",
   471  		`charm "a" relation "innocuous" using a reserved interface: "juju"`,
   472  	}, {
   473  		"peers:\n  innocuous: juju",
   474  		`charm "a" relation "innocuous" using a reserved interface: "juju"`,
   475  	}, {
   476  		"provides:\n  innocuous: juju-snap",
   477  		`charm "a" relation "innocuous" using a reserved interface: "juju-snap"`,
   478  	}, {
   479  		"peers:\n  innocuous: juju-snap",
   480  		`charm "a" relation "innocuous" using a reserved interface: "juju-snap"`,
   481  	},
   482  }
   483  
   484  func (s *MetaSuite) TestRelationsConstraints(c *gc.C) {
   485  	check := func(s, e string) {
   486  		meta, err := charm.ReadMeta(strings.NewReader(s))
   487  		if e != "" {
   488  			c.Assert(err, gc.ErrorMatches, e)
   489  			c.Assert(meta, gc.IsNil)
   490  		} else {
   491  			c.Assert(err, gc.IsNil)
   492  			c.Assert(meta, gc.NotNil)
   493  		}
   494  	}
   495  	prefix := "name: a\nsummary: b\ndescription: c\n"
   496  	for i, t := range relationsConstraintsTests {
   497  		c.Logf("test %d", i)
   498  		check(prefix+t.rels, t.err)
   499  		check(prefix+"subordinate: true\n"+t.rels, t.err)
   500  	}
   501  	// The juju-* namespace is accessible to container-scoped require
   502  	// relations on subordinate charms.
   503  	check(prefix+`
   504  subordinate: true
   505  requires:
   506    juju-info:
   507      interface: juju-info
   508      scope: container`, "")
   509  	// The juju-* interfaces are allowed on any require relation.
   510  	check(prefix+`
   511  requires:
   512    innocuous: juju-info`, "")
   513  }
   514  
   515  // dummyMetadata contains a minimally valid charm metadata.yaml
   516  // for testing valid and invalid series.
   517  const dummyMetadata = "name: a\nsummary: b\ndescription: c"
   518  
   519  func (s *MetaSuite) TestSeries(c *gc.C) {
   520  	// series not specified
   521  	meta, err := charm.ReadMeta(strings.NewReader(dummyMetadata))
   522  	c.Assert(err, gc.IsNil)
   523  	c.Check(meta.Series, gc.HasLen, 0)
   524  	charmMeta := fmt.Sprintf("%s\nseries:", dummyMetadata)
   525  	for _, seriesName := range []string{"precise", "trusty", "plan9"} {
   526  		charmMeta = fmt.Sprintf("%s\n    - %s", charmMeta, seriesName)
   527  	}
   528  	meta, err = charm.ReadMeta(strings.NewReader(charmMeta))
   529  	c.Assert(err, gc.IsNil)
   530  	c.Assert(meta.Series, gc.DeepEquals, []string{"precise", "trusty", "plan9"})
   531  }
   532  
   533  func (s *MetaSuite) TestInvalidSeries(c *gc.C) {
   534  	for _, seriesName := range []string{"pre-c1se", "pre^cise", "cp/m", "OpenVMS"} {
   535  		_, err := charm.ReadMeta(strings.NewReader(
   536  			fmt.Sprintf("%s\nseries:\n    - %s\n", dummyMetadata, seriesName)))
   537  		c.Assert(err, gc.NotNil)
   538  		c.Check(err, gc.ErrorMatches, `charm "a" declares invalid series: .*`)
   539  	}
   540  }
   541  
   542  func (s *MetaSuite) TestMinJujuVersion(c *gc.C) {
   543  	// series not specified
   544  	meta, err := charm.ReadMeta(strings.NewReader(dummyMetadata))
   545  	c.Assert(err, gc.IsNil)
   546  	c.Check(meta.Series, gc.HasLen, 0)
   547  	charmMeta := fmt.Sprintf("%s\nmin-juju-version: ", dummyMetadata)
   548  	vals := []version.Number{
   549  		{Major: 1, Minor: 25},
   550  		{Major: 1, Minor: 25, Tag: "alpha"},
   551  		{Major: 1, Minor: 25, Patch: 1},
   552  	}
   553  	for _, ver := range vals {
   554  		val := charmMeta + ver.String()
   555  		meta, err = charm.ReadMeta(strings.NewReader(val))
   556  		c.Assert(err, gc.IsNil)
   557  		c.Assert(meta.MinJujuVersion, gc.Equals, ver)
   558  	}
   559  }
   560  
   561  func (s *MetaSuite) TestInvalidMinJujuVersion(c *gc.C) {
   562  	_, err := charm.ReadMeta(strings.NewReader(dummyMetadata + "\nmin-juju-version: invalid-version"))
   563  
   564  	c.Check(err, gc.ErrorMatches, `invalid min-juju-version: invalid version "invalid-version"`)
   565  }
   566  
   567  func (s *MetaSuite) TestNoMinJujuVersion(c *gc.C) {
   568  	meta, err := charm.ReadMeta(strings.NewReader(dummyMetadata))
   569  	c.Assert(err, jc.ErrorIsNil)
   570  	c.Check(meta.MinJujuVersion, gc.Equals, version.Zero)
   571  }
   572  
   573  func (s *MetaSuite) TestCheckMismatchedRelationName(c *gc.C) {
   574  	// This  Check case cannot be covered by the above
   575  	// TestRelationsConstraints tests.
   576  	meta := charm.Meta{
   577  		Name: "foo",
   578  		Provides: map[string]charm.Relation{
   579  			"foo": {
   580  				Name:      "foo",
   581  				Role:      charm.RolePeer,
   582  				Interface: "x",
   583  				Limit:     1,
   584  				Scope:     charm.ScopeGlobal,
   585  			},
   586  		},
   587  	}
   588  	err := meta.Check()
   589  	c.Assert(err, gc.ErrorMatches, `charm "foo" has mismatched role "peer"; expected "provider"`)
   590  }
   591  
   592  func (s *MetaSuite) TestCheckMismatchedRole(c *gc.C) {
   593  	// This  Check case cannot be covered by the above
   594  	// TestRelationsConstraints tests.
   595  	meta := charm.Meta{
   596  		Name: "foo",
   597  		Provides: map[string]charm.Relation{
   598  			"foo": {
   599  				Role:      charm.RolePeer,
   600  				Interface: "foo",
   601  				Limit:     1,
   602  				Scope:     charm.ScopeGlobal,
   603  			},
   604  		},
   605  	}
   606  	err := meta.Check()
   607  	c.Assert(err, gc.ErrorMatches, `charm "foo" has mismatched relation name ""; expected "foo"`)
   608  }
   609  
   610  func (s *MetaSuite) TestCheckMismatchedExtraBindingName(c *gc.C) {
   611  	meta := charm.Meta{
   612  		Name: "foo",
   613  		ExtraBindings: map[string]charm.ExtraBinding{
   614  			"foo": {Name: "bar"},
   615  		},
   616  	}
   617  	err := meta.Check()
   618  	c.Assert(err, gc.ErrorMatches, `charm "foo" has invalid extra bindings: mismatched extra binding name: got "bar", expected "foo"`)
   619  }
   620  
   621  func (s *MetaSuite) TestCheckEmptyNameKeyOrEmptyExtraBindingName(c *gc.C) {
   622  	meta := charm.Meta{
   623  		Name:          "foo",
   624  		ExtraBindings: map[string]charm.ExtraBinding{"": {Name: "bar"}},
   625  	}
   626  	err := meta.Check()
   627  	expectedError := `charm "foo" has invalid extra bindings: missing binding name`
   628  	c.Assert(err, gc.ErrorMatches, expectedError)
   629  
   630  	meta.ExtraBindings = map[string]charm.ExtraBinding{"bar": {Name: ""}}
   631  	err = meta.Check()
   632  	c.Assert(err, gc.ErrorMatches, expectedError)
   633  }
   634  
   635  // Test rewriting of a given interface specification into long form.
   636  //
   637  // InterfaceExpander uses `coerce` to do one of two things:
   638  //
   639  //   - Rewrite shorthand to the long form used for actual storage
   640  //   - Fills in defaults, including a configurable `limit`
   641  //
   642  // This test ensures test coverage on each of these branches, along
   643  // with ensuring the conversion object properly raises SchemaError
   644  // exceptions on invalid data.
   645  func (s *MetaSuite) TestIfaceExpander(c *gc.C) {
   646  	e := charm.IfaceExpander(nil)
   647  
   648  	path := []string{"<pa", "th>"}
   649  
   650  	// Shorthand is properly rewritten
   651  	v, err := e.Coerce("http", path)
   652  	c.Assert(err, gc.IsNil)
   653  	c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": nil, "optional": false, "scope": string(charm.ScopeGlobal)})
   654  
   655  	// Defaults are properly applied
   656  	v, err = e.Coerce(map[string]interface{}{"interface": "http"}, path)
   657  	c.Assert(err, gc.IsNil)
   658  	c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": nil, "optional": false, "scope": string(charm.ScopeGlobal)})
   659  
   660  	v, err = e.Coerce(map[string]interface{}{"interface": "http", "limit": 2}, path)
   661  	c.Assert(err, gc.IsNil)
   662  	c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": int64(2), "optional": false, "scope": string(charm.ScopeGlobal)})
   663  
   664  	v, err = e.Coerce(map[string]interface{}{"interface": "http", "optional": true}, path)
   665  	c.Assert(err, gc.IsNil)
   666  	c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": nil, "optional": true, "scope": string(charm.ScopeGlobal)})
   667  
   668  	// Invalid data raises an error.
   669  	v, err = e.Coerce(42, path)
   670  	c.Assert(err, gc.ErrorMatches, `<path>: expected map, got int\(42\)`)
   671  
   672  	v, err = e.Coerce(map[string]interface{}{"interface": "http", "optional": nil}, path)
   673  	c.Assert(err, gc.ErrorMatches, "<path>.optional: expected bool, got nothing")
   674  
   675  	v, err = e.Coerce(map[string]interface{}{"interface": "http", "limit": "none, really"}, path)
   676  	c.Assert(err, gc.ErrorMatches, "<path>.limit: unexpected value.*")
   677  
   678  	// Can change default limit
   679  	e = charm.IfaceExpander(1)
   680  	v, err = e.Coerce(map[string]interface{}{"interface": "http"}, path)
   681  	c.Assert(err, gc.IsNil)
   682  	c.Assert(v, jc.DeepEquals, map[string]interface{}{"interface": "http", "limit": int64(1), "optional": false, "scope": string(charm.ScopeGlobal)})
   683  }
   684  
   685  func (s *MetaSuite) TestMetaHooks(c *gc.C) {
   686  	meta, err := charm.ReadMeta(repoMeta(c, "wordpress"))
   687  	c.Assert(err, gc.IsNil)
   688  	hooks := meta.Hooks()
   689  	expectedHooks := map[string]bool{
   690  		"install":                           true,
   691  		"start":                             true,
   692  		"config-changed":                    true,
   693  		"upgrade-charm":                     true,
   694  		"stop":                              true,
   695  		"collect-metrics":                   true,
   696  		"meter-status-changed":              true,
   697  		"leader-elected":                    true,
   698  		"leader-deposed":                    true,
   699  		"leader-settings-changed":           true,
   700  		"update-status":                     true,
   701  		"cache-relation-joined":             true,
   702  		"cache-relation-changed":            true,
   703  		"cache-relation-departed":           true,
   704  		"cache-relation-broken":             true,
   705  		"db-relation-joined":                true,
   706  		"db-relation-changed":               true,
   707  		"db-relation-departed":              true,
   708  		"db-relation-broken":                true,
   709  		"logging-dir-relation-joined":       true,
   710  		"logging-dir-relation-changed":      true,
   711  		"logging-dir-relation-departed":     true,
   712  		"logging-dir-relation-broken":       true,
   713  		"monitoring-port-relation-joined":   true,
   714  		"monitoring-port-relation-changed":  true,
   715  		"monitoring-port-relation-departed": true,
   716  		"monitoring-port-relation-broken":   true,
   717  		"url-relation-joined":               true,
   718  		"url-relation-changed":              true,
   719  		"url-relation-departed":             true,
   720  		"url-relation-broken":               true,
   721  	}
   722  	c.Assert(hooks, jc.DeepEquals, expectedHooks)
   723  }
   724  
   725  func (s *MetaSuite) TestCodecRoundTripEmpty(c *gc.C) {
   726  	for i, codec := range codecs {
   727  		c.Logf("codec %d", i)
   728  		empty_input := charm.Meta{}
   729  		data, err := codec.Marshal(empty_input)
   730  		c.Assert(err, gc.IsNil)
   731  		var empty_output charm.Meta
   732  		err = codec.Unmarshal(data, &empty_output)
   733  		c.Assert(err, gc.IsNil)
   734  		c.Assert(empty_input, jc.DeepEquals, empty_output)
   735  	}
   736  }
   737  
   738  func (s *MetaSuite) TestCodecRoundTrip(c *gc.C) {
   739  	var input = charm.Meta{
   740  		Name:        "Foo",
   741  		Summary:     "Bar",
   742  		Description: "Baz",
   743  		Subordinate: true,
   744  		Provides: map[string]charm.Relation{
   745  			"qux": {
   746  				Name:      "qux",
   747  				Role:      charm.RoleProvider,
   748  				Interface: "quxx",
   749  				Optional:  true,
   750  				Limit:     42,
   751  				Scope:     charm.ScopeGlobal,
   752  			},
   753  		},
   754  		Requires: map[string]charm.Relation{
   755  			"frob": {
   756  				Name:      "frob",
   757  				Role:      charm.RoleRequirer,
   758  				Interface: "quxx",
   759  				Optional:  true,
   760  				Limit:     42,
   761  				Scope:     charm.ScopeContainer,
   762  			},
   763  		},
   764  		Peers: map[string]charm.Relation{
   765  			"arble": {
   766  				Name:      "arble",
   767  				Role:      charm.RolePeer,
   768  				Interface: "quxx",
   769  				Optional:  true,
   770  				Limit:     42,
   771  				Scope:     charm.ScopeGlobal,
   772  			},
   773  		},
   774  		ExtraBindings: map[string]charm.ExtraBinding{
   775  			"b1": {Name: "b1"},
   776  			"b2": {Name: "b2"},
   777  		},
   778  		Categories: []string{"quxxxx", "quxxxxx"},
   779  		Tags:       []string{"openstack", "storage"},
   780  		Terms:      []string{"test-term/1", "test-term/2"},
   781  	}
   782  	for i, codec := range codecs {
   783  		c.Logf("codec %d", i)
   784  		data, err := codec.Marshal(input)
   785  		c.Assert(err, gc.IsNil)
   786  		var output charm.Meta
   787  		err = codec.Unmarshal(data, &output)
   788  		c.Assert(err, gc.IsNil)
   789  		c.Assert(output, jc.DeepEquals, input, gc.Commentf("data: %q", data))
   790  	}
   791  }
   792  
   793  var implementedByTests = []struct {
   794  	ifce     string
   795  	name     string
   796  	role     charm.RelationRole
   797  	scope    charm.RelationScope
   798  	match    bool
   799  	implicit bool
   800  }{
   801  	{"ifce-pro", "pro", charm.RoleProvider, charm.ScopeGlobal, true, false},
   802  	{"blah", "pro", charm.RoleProvider, charm.ScopeGlobal, false, false},
   803  	{"ifce-pro", "blah", charm.RoleProvider, charm.ScopeGlobal, false, false},
   804  	{"ifce-pro", "pro", charm.RoleRequirer, charm.ScopeGlobal, false, false},
   805  	{"ifce-pro", "pro", charm.RoleProvider, charm.ScopeContainer, true, false},
   806  
   807  	{"juju-info", "juju-info", charm.RoleProvider, charm.ScopeGlobal, true, true},
   808  	{"blah", "juju-info", charm.RoleProvider, charm.ScopeGlobal, false, false},
   809  	{"juju-info", "blah", charm.RoleProvider, charm.ScopeGlobal, false, false},
   810  	{"juju-info", "juju-info", charm.RoleRequirer, charm.ScopeGlobal, false, false},
   811  	{"juju-info", "juju-info", charm.RoleProvider, charm.ScopeContainer, true, true},
   812  
   813  	{"ifce-req", "req", charm.RoleRequirer, charm.ScopeGlobal, true, false},
   814  	{"blah", "req", charm.RoleRequirer, charm.ScopeGlobal, false, false},
   815  	{"ifce-req", "blah", charm.RoleRequirer, charm.ScopeGlobal, false, false},
   816  	{"ifce-req", "req", charm.RolePeer, charm.ScopeGlobal, false, false},
   817  	{"ifce-req", "req", charm.RoleRequirer, charm.ScopeContainer, true, false},
   818  
   819  	{"juju-info", "info", charm.RoleRequirer, charm.ScopeContainer, true, false},
   820  	{"blah", "info", charm.RoleRequirer, charm.ScopeContainer, false, false},
   821  	{"juju-info", "blah", charm.RoleRequirer, charm.ScopeContainer, false, false},
   822  	{"juju-info", "info", charm.RolePeer, charm.ScopeContainer, false, false},
   823  	{"juju-info", "info", charm.RoleRequirer, charm.ScopeGlobal, false, false},
   824  
   825  	{"ifce-peer", "peer", charm.RolePeer, charm.ScopeGlobal, true, false},
   826  	{"blah", "peer", charm.RolePeer, charm.ScopeGlobal, false, false},
   827  	{"ifce-peer", "blah", charm.RolePeer, charm.ScopeGlobal, false, false},
   828  	{"ifce-peer", "peer", charm.RoleProvider, charm.ScopeGlobal, false, false},
   829  	{"ifce-peer", "peer", charm.RolePeer, charm.ScopeContainer, true, false},
   830  }
   831  
   832  func (s *MetaSuite) TestImplementedBy(c *gc.C) {
   833  	for i, t := range implementedByTests {
   834  		c.Logf("test %d", i)
   835  		r := charm.Relation{
   836  			Interface: t.ifce,
   837  			Name:      t.name,
   838  			Role:      t.role,
   839  			Scope:     t.scope,
   840  		}
   841  		c.Assert(r.ImplementedBy(&dummyCharm{}), gc.Equals, t.match)
   842  		c.Assert(r.IsImplicit(), gc.Equals, t.implicit)
   843  	}
   844  }
   845  
   846  var metaYAMLMarshalTests = []struct {
   847  	about string
   848  	yaml  string
   849  }{{
   850  	about: "minimal charm",
   851  	yaml: `
   852  name: minimal
   853  description: d
   854  summary: s
   855  `,
   856  }, {
   857  	about: "charm with lots of stuff",
   858  	yaml: `
   859  name: big
   860  description: d
   861  summary: s
   862  subordinate: true
   863  provides:
   864      provideSimple: someinterface
   865      provideLessSimple:
   866          interface: anotherinterface
   867          optional: true
   868          scope: container
   869          limit: 3
   870  requires:
   871      requireSimple: someinterface
   872      requireLessSimple:
   873          interface: anotherinterface
   874          optional: true
   875          scope: container
   876          limit: 3
   877  peers:
   878      peerSimple: someinterface
   879      peerLessSimple:
   880          interface: peery
   881          optional: true
   882  extra-bindings:
   883      extraBar:
   884      extraFoo1:
   885  categories: [c1, c1]
   886  tags: [t1, t2]
   887  series:
   888      - someseries
   889  resources:
   890      foo:
   891          description: 'a description'
   892          filename: 'x.zip'
   893      bar:
   894          filename: 'y.tgz'
   895          type: file
   896  `,
   897  }}
   898  
   899  func (s *MetaSuite) TestYAMLMarshal(c *gc.C) {
   900  	for i, test := range metaYAMLMarshalTests {
   901  		c.Logf("test %d: %s", i, test.about)
   902  		ch, err := charm.ReadMeta(strings.NewReader(test.yaml))
   903  		c.Assert(err, gc.IsNil)
   904  		gotYAML, err := yaml.Marshal(ch)
   905  		c.Assert(err, gc.IsNil)
   906  		gotCh, err := charm.ReadMeta(bytes.NewReader(gotYAML))
   907  		c.Assert(err, gc.IsNil)
   908  		c.Assert(gotCh, jc.DeepEquals, ch)
   909  	}
   910  }
   911  
   912  func (s *MetaSuite) TestYAMLMarshalV2(c *gc.C) {
   913  	for i, test := range metaYAMLMarshalTests {
   914  		c.Logf("test %d: %s", i, test.about)
   915  		ch, err := charm.ReadMeta(strings.NewReader(test.yaml))
   916  		c.Assert(err, gc.IsNil)
   917  		gotYAML, err := yamlv2.Marshal(ch)
   918  		c.Assert(err, gc.IsNil)
   919  		gotCh, err := charm.ReadMeta(bytes.NewReader(gotYAML))
   920  		c.Assert(err, gc.IsNil)
   921  		c.Assert(gotCh, jc.DeepEquals, ch)
   922  	}
   923  }
   924  
   925  func (s *MetaSuite) TestYAMLMarshalSimpleRelationOrExtraBinding(c *gc.C) {
   926  	// Check that a simple relation / extra-binding gets marshaled as a string.
   927  	chYAML := `
   928  name: minimal
   929  description: d
   930  summary: s
   931  provides:
   932      server: http
   933  requires:
   934      client: http
   935  peers:
   936       me: http
   937  extra-bindings:
   938       foo:
   939  `
   940  	ch, err := charm.ReadMeta(strings.NewReader(chYAML))
   941  	c.Assert(err, gc.IsNil)
   942  	gotYAML, err := yaml.Marshal(ch)
   943  	c.Assert(err, gc.IsNil)
   944  
   945  	var x interface{}
   946  	err = yaml.Unmarshal(gotYAML, &x)
   947  	c.Assert(err, gc.IsNil)
   948  	c.Assert(x, jc.DeepEquals, map[interface{}]interface{}{
   949  		"name":        "minimal",
   950  		"description": "d",
   951  		"summary":     "s",
   952  		"provides": map[interface{}]interface{}{
   953  			"server": "http",
   954  		},
   955  		"requires": map[interface{}]interface{}{
   956  			"client": "http",
   957  		},
   958  		"peers": map[interface{}]interface{}{
   959  			"me": "http",
   960  		},
   961  		"extra-bindings": map[interface{}]interface{}{
   962  			"foo": nil,
   963  		},
   964  	})
   965  }
   966  
   967  func (s *MetaSuite) TestStorage(c *gc.C) {
   968  	// "type" is the only required attribute for storage.
   969  	meta, err := charm.ReadMeta(strings.NewReader(`
   970  name: a
   971  summary: b
   972  description: c
   973  storage:
   974      store0:
   975          description: woo tee bix
   976          type: block
   977      store1:
   978          type: filesystem
   979  `))
   980  	c.Assert(err, gc.IsNil)
   981  	c.Assert(meta.Storage, gc.DeepEquals, map[string]charm.Storage{
   982  		"store0": {
   983  			Name:        "store0",
   984  			Description: "woo tee bix",
   985  			Type:        charm.StorageBlock,
   986  			CountMin:    1, // singleton
   987  			CountMax:    1,
   988  		},
   989  		"store1": {
   990  			Name:     "store1",
   991  			Type:     charm.StorageFilesystem,
   992  			CountMin: 1, // singleton
   993  			CountMax: 1,
   994  		},
   995  	})
   996  }
   997  
   998  func (s *MetaSuite) TestStorageErrors(c *gc.C) {
   999  	prefix := `
  1000  name: a
  1001  summary: b
  1002  description: c
  1003  storage:
  1004   store-bad:
  1005  `[1:]
  1006  
  1007  	type test struct {
  1008  		desc string
  1009  		yaml string
  1010  		err  string
  1011  	}
  1012  
  1013  	tests := []test{{
  1014  		desc: "type is required",
  1015  		yaml: "  required: false",
  1016  		err:  "metadata: storage.store-bad.type: unexpected value <nil>",
  1017  	}, {
  1018  		desc: "range must be an integer, or integer range (1)",
  1019  		yaml: "  type: filesystem\n  multiple:\n   range: woat",
  1020  		err:  `metadata: storage.store-bad.multiple.range: value "woat" does not match 'm', 'm-n', or 'm\+'`,
  1021  	}, {
  1022  		desc: "range must be an integer, or integer range (2)",
  1023  		yaml: "  type: filesystem\n  multiple:\n   range: 0-abc",
  1024  		err:  `metadata: storage.store-bad.multiple.range: value "0-abc" does not match 'm', 'm-n', or 'm\+'`,
  1025  	}, {
  1026  		desc: "range must be non-negative",
  1027  		yaml: "  type: filesystem\n  multiple:\n    range: -1",
  1028  		err:  `metadata: storage.store-bad.multiple.range: invalid count -1`,
  1029  	}, {
  1030  		desc: "range must be positive",
  1031  		yaml: "  type: filesystem\n  multiple:\n    range: 0",
  1032  		err:  `metadata: storage.store-bad.multiple.range: invalid count 0`,
  1033  	}, {
  1034  		desc: "location cannot be specified for block type storage",
  1035  		yaml: "  type: block\n  location: /dev/sdc",
  1036  		err:  `charm "a" storage "store-bad": location may not be specified for "type: block"`,
  1037  	}, {
  1038  		desc: "minimum size must parse correctly",
  1039  		yaml: "  type: block\n  minimum-size: foo",
  1040  		err:  `metadata: expected a non-negative number, got "foo"`,
  1041  	}, {
  1042  		desc: "minimum size must have valid suffix",
  1043  		yaml: "  type: block\n  minimum-size: 10Q",
  1044  		err:  `metadata: invalid multiplier suffix "Q", expected one of MGTPEZY`,
  1045  	}, {
  1046  		desc: "properties must contain valid values",
  1047  		yaml: "  type: block\n  properties: [transient, foo]",
  1048  		err:  `metadata: .* unexpected value "foo"`,
  1049  	}}
  1050  
  1051  	for i, test := range tests {
  1052  		c.Logf("test %d: %s", i, test.desc)
  1053  		c.Logf("\n%s\n", prefix+test.yaml)
  1054  		_, err := charm.ReadMeta(strings.NewReader(prefix + test.yaml))
  1055  		c.Assert(err, gc.ErrorMatches, test.err)
  1056  	}
  1057  }
  1058  
  1059  func (s *MetaSuite) TestStorageCount(c *gc.C) {
  1060  	testStorageCount := func(count string, min, max int) {
  1061  		meta, err := charm.ReadMeta(strings.NewReader(fmt.Sprintf(`
  1062  name: a
  1063  summary: b
  1064  description: c
  1065  storage:
  1066      store0:
  1067          type: filesystem
  1068          multiple:
  1069              range: %s
  1070  `, count)))
  1071  		c.Assert(err, gc.IsNil)
  1072  		store := meta.Storage["store0"]
  1073  		c.Assert(store, gc.NotNil)
  1074  		c.Assert(store.CountMin, gc.Equals, min)
  1075  		c.Assert(store.CountMax, gc.Equals, max)
  1076  	}
  1077  	testStorageCount("1", 1, 1)
  1078  	testStorageCount("0-1", 0, 1)
  1079  	testStorageCount("1-1", 1, 1)
  1080  	testStorageCount("1+", 1, -1)
  1081  	// n- is equivalent to n+
  1082  	testStorageCount("1-", 1, -1)
  1083  }
  1084  
  1085  func (s *MetaSuite) TestStorageLocation(c *gc.C) {
  1086  	meta, err := charm.ReadMeta(strings.NewReader(`
  1087  name: a
  1088  summary: b
  1089  description: c
  1090  storage:
  1091      store0:
  1092          type: filesystem
  1093          location: /var/lib/things
  1094  `))
  1095  	c.Assert(err, gc.IsNil)
  1096  	store := meta.Storage["store0"]
  1097  	c.Assert(store, gc.NotNil)
  1098  	c.Assert(store.Location, gc.Equals, "/var/lib/things")
  1099  }
  1100  
  1101  func (s *MetaSuite) TestStorageMinimumSize(c *gc.C) {
  1102  	meta, err := charm.ReadMeta(strings.NewReader(`
  1103  name: a
  1104  summary: b
  1105  description: c
  1106  storage:
  1107      store0:
  1108          type: filesystem
  1109          minimum-size: 10G
  1110  `))
  1111  	c.Assert(err, gc.IsNil)
  1112  	store := meta.Storage["store0"]
  1113  	c.Assert(store, gc.NotNil)
  1114  	c.Assert(store.MinimumSize, gc.Equals, uint64(10*1024))
  1115  }
  1116  
  1117  func (s *MetaSuite) TestStorageProperties(c *gc.C) {
  1118  	meta, err := charm.ReadMeta(strings.NewReader(`
  1119  name: a
  1120  summary: b
  1121  description: c
  1122  storage:
  1123      store0:
  1124          type: filesystem
  1125          properties: [transient]
  1126  `))
  1127  	c.Assert(err, gc.IsNil)
  1128  	store := meta.Storage["store0"]
  1129  	c.Assert(store, gc.NotNil)
  1130  	c.Assert(store.Properties, jc.SameContents, []string{"transient"})
  1131  }
  1132  
  1133  func (s *MetaSuite) TestExtraBindings(c *gc.C) {
  1134  	meta, err := charm.ReadMeta(strings.NewReader(`
  1135  name: a
  1136  summary: b
  1137  description: c
  1138  extra-bindings:
  1139      endpoint-1:
  1140      foo:
  1141      bar-42:
  1142  `))
  1143  	c.Assert(err, gc.IsNil)
  1144  	c.Assert(meta.ExtraBindings, gc.DeepEquals, map[string]charm.ExtraBinding{
  1145  		"endpoint-1": {
  1146  			Name: "endpoint-1",
  1147  		},
  1148  		"foo": {
  1149  			Name: "foo",
  1150  		},
  1151  		"bar-42": {
  1152  			Name: "bar-42",
  1153  		},
  1154  	})
  1155  }
  1156  
  1157  func (s *MetaSuite) TestExtraBindingsEmptyMapError(c *gc.C) {
  1158  	meta, err := charm.ReadMeta(strings.NewReader(`
  1159  name: a
  1160  summary: b
  1161  description: c
  1162  extra-bindings:
  1163  `))
  1164  	c.Assert(err, gc.ErrorMatches, "metadata: extra-bindings: expected map, got nothing")
  1165  	c.Assert(meta, gc.IsNil)
  1166  }
  1167  
  1168  func (s *MetaSuite) TestExtraBindingsNonEmptyValueError(c *gc.C) {
  1169  	meta, err := charm.ReadMeta(strings.NewReader(`
  1170  name: a
  1171  summary: b
  1172  description: c
  1173  extra-bindings:
  1174      foo: 42
  1175  `))
  1176  	c.Assert(err, gc.ErrorMatches, `metadata: extra-bindings.foo: expected empty value, got int\(42\)`)
  1177  	c.Assert(meta, gc.IsNil)
  1178  }
  1179  
  1180  func (s *MetaSuite) TestExtraBindingsEmptyNameError(c *gc.C) {
  1181  	meta, err := charm.ReadMeta(strings.NewReader(`
  1182  name: a
  1183  summary: b
  1184  description: c
  1185  extra-bindings:
  1186      "":
  1187  `))
  1188  	c.Assert(err, gc.ErrorMatches, `metadata: extra-bindings: expected non-empty binding name, got string\(""\)`)
  1189  	c.Assert(meta, gc.IsNil)
  1190  }
  1191  
  1192  func (s *MetaSuite) TestPayloadClasses(c *gc.C) {
  1193  	meta, err := charm.ReadMeta(strings.NewReader(`
  1194  name: a
  1195  summary: b
  1196  description: c
  1197  payloads:
  1198      monitor:
  1199          type: docker
  1200      kvm-guest:
  1201          type: kvm
  1202  `))
  1203  	c.Assert(err, gc.IsNil)
  1204  
  1205  	c.Check(meta.PayloadClasses, jc.DeepEquals, map[string]charm.PayloadClass{
  1206  		"monitor": charm.PayloadClass{
  1207  			Name: "monitor",
  1208  			Type: "docker",
  1209  		},
  1210  		"kvm-guest": charm.PayloadClass{
  1211  			Name: "kvm-guest",
  1212  			Type: "kvm",
  1213  		},
  1214  	})
  1215  }
  1216  
  1217  func (s *MetaSuite) TestResources(c *gc.C) {
  1218  	meta, err := charm.ReadMeta(strings.NewReader(`
  1219  name: a
  1220  summary: b
  1221  description: c
  1222  resources:
  1223      resource-name:
  1224          type: file
  1225          filename: filename.tgz
  1226          description: "One line that is useful when operators need to push it."
  1227      other-resource:
  1228          type: file
  1229          filename: other.zip
  1230  `))
  1231  	c.Assert(err, gc.IsNil)
  1232  
  1233  	c.Check(meta.Resources, jc.DeepEquals, map[string]resource.Meta{
  1234  		"resource-name": resource.Meta{
  1235  			Name:        "resource-name",
  1236  			Type:        resource.TypeFile,
  1237  			Path:        "filename.tgz",
  1238  			Description: "One line that is useful when operators need to push it.",
  1239  		},
  1240  		"other-resource": resource.Meta{
  1241  			Name: "other-resource",
  1242  			Type: resource.TypeFile,
  1243  			Path: "other.zip",
  1244  		},
  1245  	})
  1246  }
  1247  
  1248  func (s *MetaSuite) TestParseResourceMetaOkay(c *gc.C) {
  1249  	name := "my-resource"
  1250  	data := map[string]interface{}{
  1251  		"type":        "file",
  1252  		"filename":    "filename.tgz",
  1253  		"description": "One line that is useful when operators need to push it.",
  1254  	}
  1255  	res, err := charm.ParseResourceMeta(name, data)
  1256  	c.Assert(err, jc.ErrorIsNil)
  1257  
  1258  	c.Check(res, jc.DeepEquals, resource.Meta{
  1259  		Name:        "my-resource",
  1260  		Type:        resource.TypeFile,
  1261  		Path:        "filename.tgz",
  1262  		Description: "One line that is useful when operators need to push it.",
  1263  	})
  1264  }
  1265  
  1266  func (s *MetaSuite) TestParseResourceMetaMissingName(c *gc.C) {
  1267  	name := ""
  1268  	data := map[string]interface{}{
  1269  		"type":        "file",
  1270  		"filename":    "filename.tgz",
  1271  		"description": "One line that is useful when operators need to push it.",
  1272  	}
  1273  	res, err := charm.ParseResourceMeta(name, data)
  1274  	c.Assert(err, jc.ErrorIsNil)
  1275  
  1276  	c.Check(res, jc.DeepEquals, resource.Meta{
  1277  		Name:        "",
  1278  		Type:        resource.TypeFile,
  1279  		Path:        "filename.tgz",
  1280  		Description: "One line that is useful when operators need to push it.",
  1281  	})
  1282  }
  1283  
  1284  func (s *MetaSuite) TestParseResourceMetaMissingType(c *gc.C) {
  1285  	name := "my-resource"
  1286  	data := map[string]interface{}{
  1287  		"filename":    "filename.tgz",
  1288  		"description": "One line that is useful when operators need to push it.",
  1289  	}
  1290  	res, err := charm.ParseResourceMeta(name, data)
  1291  	c.Assert(err, jc.ErrorIsNil)
  1292  
  1293  	c.Check(res, jc.DeepEquals, resource.Meta{
  1294  		Name: "my-resource",
  1295  		// Type is the zero value.
  1296  		Path:        "filename.tgz",
  1297  		Description: "One line that is useful when operators need to push it.",
  1298  	})
  1299  }
  1300  
  1301  func (s *MetaSuite) TestParseResourceMetaEmptyType(c *gc.C) {
  1302  	name := "my-resource"
  1303  	data := map[string]interface{}{
  1304  		"type":        "",
  1305  		"filename":    "filename.tgz",
  1306  		"description": "One line that is useful when operators need to push it.",
  1307  	}
  1308  	_, err := charm.ParseResourceMeta(name, data)
  1309  
  1310  	c.Check(err, gc.ErrorMatches, `unsupported resource type .*`)
  1311  }
  1312  
  1313  func (s *MetaSuite) TestParseResourceMetaUnknownType(c *gc.C) {
  1314  	name := "my-resource"
  1315  	data := map[string]interface{}{
  1316  		"type":        "spam",
  1317  		"filename":    "filename.tgz",
  1318  		"description": "One line that is useful when operators need to push it.",
  1319  	}
  1320  	_, err := charm.ParseResourceMeta(name, data)
  1321  
  1322  	c.Check(err, gc.ErrorMatches, `unsupported resource type .*`)
  1323  }
  1324  
  1325  func (s *MetaSuite) TestParseResourceMetaMissingPath(c *gc.C) {
  1326  	name := "my-resource"
  1327  	data := map[string]interface{}{
  1328  		"type":        "file",
  1329  		"description": "One line that is useful when operators need to push it.",
  1330  	}
  1331  	res, err := charm.ParseResourceMeta(name, data)
  1332  	c.Assert(err, jc.ErrorIsNil)
  1333  
  1334  	c.Check(res, jc.DeepEquals, resource.Meta{
  1335  		Name:        "my-resource",
  1336  		Type:        resource.TypeFile,
  1337  		Path:        "",
  1338  		Description: "One line that is useful when operators need to push it.",
  1339  	})
  1340  }
  1341  
  1342  func (s *MetaSuite) TestParseResourceMetaMissingComment(c *gc.C) {
  1343  	name := "my-resource"
  1344  	data := map[string]interface{}{
  1345  		"type":     "file",
  1346  		"filename": "filename.tgz",
  1347  	}
  1348  	res, err := charm.ParseResourceMeta(name, data)
  1349  	c.Assert(err, jc.ErrorIsNil)
  1350  
  1351  	c.Check(res, jc.DeepEquals, resource.Meta{
  1352  		Name:        "my-resource",
  1353  		Type:        resource.TypeFile,
  1354  		Path:        "filename.tgz",
  1355  		Description: "",
  1356  	})
  1357  }
  1358  
  1359  func (s *MetaSuite) TestParseResourceMetaEmpty(c *gc.C) {
  1360  	name := "my-resource"
  1361  	data := make(map[string]interface{})
  1362  	res, err := charm.ParseResourceMeta(name, data)
  1363  	c.Assert(err, jc.ErrorIsNil)
  1364  
  1365  	c.Check(res, jc.DeepEquals, resource.Meta{
  1366  		Name: "my-resource",
  1367  	})
  1368  }
  1369  
  1370  func (s *MetaSuite) TestParseResourceMetaNil(c *gc.C) {
  1371  	name := "my-resource"
  1372  	var data map[string]interface{}
  1373  	res, err := charm.ParseResourceMeta(name, data)
  1374  	c.Assert(err, jc.ErrorIsNil)
  1375  
  1376  	c.Check(res, jc.DeepEquals, resource.Meta{
  1377  		Name: "my-resource",
  1378  	})
  1379  }
  1380  
  1381  type dummyCharm struct{}
  1382  
  1383  func (c *dummyCharm) Config() *charm.Config {
  1384  	panic("unused")
  1385  }
  1386  
  1387  func (c *dummyCharm) Metrics() *charm.Metrics {
  1388  	panic("unused")
  1389  }
  1390  
  1391  func (c *dummyCharm) Actions() *charm.Actions {
  1392  	panic("unused")
  1393  }
  1394  
  1395  func (c *dummyCharm) Revision() int {
  1396  	panic("unused")
  1397  }
  1398  
  1399  func (c *dummyCharm) Meta() *charm.Meta {
  1400  	return &charm.Meta{
  1401  		Provides: map[string]charm.Relation{
  1402  			"pro": {Interface: "ifce-pro", Scope: charm.ScopeGlobal},
  1403  		},
  1404  		Requires: map[string]charm.Relation{
  1405  			"req":  {Interface: "ifce-req", Scope: charm.ScopeGlobal},
  1406  			"info": {Interface: "juju-info", Scope: charm.ScopeContainer},
  1407  		},
  1408  		Peers: map[string]charm.Relation{
  1409  			"peer": {Interface: "ifce-peer", Scope: charm.ScopeGlobal},
  1410  		},
  1411  	}
  1412  }