1
// Copyright 2012, 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
package testing // import "gopkg.in/juju/charmrepo.v2-unstable/testing"
16
"github.com/juju/testing/filetesting"
17
gc "gopkg.in/check.v1"
19
"gopkg.in/juju/charm.v6-unstable"
20
"gopkg.in/juju/charm.v6-unstable/resource"
24
// Charm holds a charm for testing. It does not
25
// have a representation on disk by default, but
26
// can be written to disk using Archive and its ExpandTo
27
// method. It implements the charm.Charm interface.
29
// All methods on Charm may be called concurrently.
33
actions *charm.Actions
34
metrics *charm.Metrics
37
files filetesting.Entries
39
makeArchiveOnce sync.Once
41
archive *charm.CharmArchive
44
// CharmSpec holds the specification for a charm. The fields
45
// hold data in YAML format.
46
type CharmSpec struct {
47
// Meta holds the contents of metadata.yaml.
50
// Config holds the contents of config.yaml.
53
// Actions holds the contents of actions.yaml.
56
// Metrics holds the contents of metrics.yaml.
59
// Files holds any additional files that should be
60
// added to the charm. If this is nil, a minimal set
61
// of files will be added to ensure the charm is readable.
62
Files []filetesting.Entry
64
// Revision specifies the revision of the charm.
74
// NewCharm returns a charm following the given specification.
75
func NewCharm(c *gc.C, spec CharmSpec) *Charm {
79
// newCharm is the internal version of NewCharm that
80
// doesn't take a *gc.C so it can be used in NewCharmWithMeta.
81
func newCharm(spec CharmSpec) *Charm {
83
revision: spec.Revision,
86
ch.meta, err = charm.ReadMeta(strings.NewReader(spec.Meta))
91
ch.files = append(ch.files, filetesting.File{
92
Path: "metadata.yaml",
97
if spec.Config != "" {
98
ch.config, err = charm.ReadConfig(strings.NewReader(spec.Config))
102
ch.files = append(ch.files, filetesting.File{
108
if spec.Actions != "" {
109
ch.actions, err = charm.ReadActionsYaml(strings.NewReader(spec.Actions))
113
ch.files = append(ch.files, filetesting.File{
114
Path: "actions.yaml",
119
if spec.Metrics != "" {
120
ch.metrics, err = charm.ReadMetrics(strings.NewReader(spec.Metrics))
124
ch.files = append(ch.files, filetesting.File{
125
Path: "metrics.yaml",
130
if spec.Files == nil {
131
ch.files = append(ch.files, filetesting.File{
132
Path: "hooks/install",
141
ch.files = append(ch.files, spec.Files...)
142
// Check for duplicates.
143
names := make(map[string]bool)
144
for _, f := range ch.files {
145
name := path.Clean(f.GetPath())
147
panic(fmt.Errorf("duplicate file entry %q", f.GetPath()))
155
// NewCharmMeta returns a charm with the given metadata.
156
// It doesn't take a *gc.C, so it can be used at init time,
157
// for example in table-driven tests.
158
func NewCharmMeta(meta *charm.Meta) *Charm {
160
meta = new(charm.Meta)
162
metaYAML, err := yaml.Marshal(meta)
166
return newCharm(CharmSpec{
167
Meta: string(metaYAML),
171
// Meta implements charm.Charm.Meta.
172
func (ch *Charm) Meta() *charm.Meta {
176
// Config implements charm.Charm.Config.
177
func (ch *Charm) Config() *charm.Config {
178
if ch.config == nil {
179
return &charm.Config{
180
Options: map[string]charm.Option{},
186
// Metrics implements charm.Charm.Metrics.
187
func (ch *Charm) Metrics() *charm.Metrics {
191
// Actions implements charm.Charm.Actions.
192
func (ch *Charm) Actions() *charm.Actions {
193
if ch.actions == nil {
194
return &charm.Actions{}
199
// Revision implements charm.Charm.Revision.
200
func (ch *Charm) Revision() int {
204
// Archive returns a charm archive holding the charm.
205
func (ch *Charm) Archive() *charm.CharmArchive {
206
ch.makeArchiveOnce.Do(ch.makeArchive)
210
// ArchiveBytes returns the contents of the charm archive
211
// holding the charm.
212
func (ch *Charm) ArchiveBytes() []byte {
213
ch.makeArchiveOnce.Do(ch.makeArchive)
214
return ch.archiveBytes
217
// ArchiveTo implements ArchiveTo as implemented
218
// by *charm.Dir, enabling the charm to be used in some APIs
219
// that check for that method.
220
func (c *Charm) ArchiveTo(w io.Writer) error {
221
_, err := w.Write(c.ArchiveBytes())
225
// Size returns the size of the charm's archive blob.
226
func (c *Charm) Size() int64 {
227
return int64(len(c.ArchiveBytes()))
230
func (ch *Charm) makeArchive() {
232
zw := zip.NewWriter(&buf)
234
for _, f := range ch.files {
237
if err := zw.Close(); err != nil {
240
// ReadCharmArchiveFromReader requires a ReaderAt, so make one.
241
r := bytes.NewReader(buf.Bytes())
243
// Actually make the charm archive.
244
archive, err := charm.ReadCharmArchiveFromReader(r, int64(buf.Len()))
248
ch.archiveBytes = buf.Bytes()
250
ch.archive.SetRevision(ch.revision)
253
func addZipEntry(zw *zip.Writer, f filetesting.Entry) {
254
h := &zip.FileHeader{
256
// Don't bother compressing - the contents are so small that
257
// it will just slow things down for no particular benefit.
261
switch f := f.(type) {
262
case filetesting.Dir:
263
h.SetMode(os.ModeDir | 0755)
264
case filetesting.File:
267
case filetesting.Symlink:
268
h.SetMode(os.ModeSymlink | 0777)
271
w, err := zw.CreateHeader(h)
276
if _, err := w.Write([]byte(contents)); err != nil {
282
// MetaWithSupportedSeries returns m with Series
283
// set to series. If m is nil, new(charm.Meta)
284
// will be used instead.
285
func MetaWithSupportedSeries(m *charm.Meta, series ...string) *charm.Meta {
293
// MetaWithRelations returns m with Provides and Requires set
294
// to the given relations, where each relation
295
// is specified as a white-space-separated
297
// role name interface
298
// where role specifies the role of the interface
299
// ("provides" or "requires"), name holds the relation
300
// name and interface holds the interface relation type.
302
// If m is nil, new(charm.Meta) will be used instead.
303
func MetaWithRelations(m *charm.Meta, relations ...string) *charm.Meta {
307
provides := make(map[string]charm.Relation)
308
requires := make(map[string]charm.Relation)
309
for _, rel := range relations {
310
r, err := parseRelation(rel)
312
panic(fmt.Errorf("bad relation %q", err))
314
if r.Role == charm.RoleProvider {
320
m.Provides = provides
321
m.Requires = requires
325
func parseRelation(s string) (charm.Relation, error) {
326
fields := strings.Fields(s)
327
if len(fields) != 3 {
328
return charm.Relation{}, errgo.Newf("wrong field count")
331
Scope: charm.ScopeGlobal,
333
Interface: fields[2],
337
r.Role = charm.RoleProvider
339
r.Role = charm.RoleRequirer
341
return charm.Relation{}, errgo.Newf("unknown role")
346
// MetaWithResources returns m with Resources set to a set of resources
347
// with the given names. If m is nil, new(charm.Meta) will be used
350
// The path and description of the resources are derived from
351
// the resource name by adding a "-file" and a " description"
352
// suffix respectively.
353
func MetaWithResources(m *charm.Meta, resources ...string) *charm.Meta {
357
m.Resources = make(map[string]resource.Meta)
358
for _, name := range resources {
359
m.Resources[name] = resource.Meta{
361
Type: resource.TypeFile,
362
Path: name + "-file",
363
Description: name + " description",