~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charmrepo.v2-unstable/testing/testcharm.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2012, 2013 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package testing // import "gopkg.in/juju/charmrepo.v2-unstable/testing"
 
5
 
 
6
import (
 
7
        "archive/zip"
 
8
        "bytes"
 
9
        "fmt"
 
10
        "io"
 
11
        "os"
 
12
        "path"
 
13
        "strings"
 
14
        "sync"
 
15
 
 
16
        "github.com/juju/testing/filetesting"
 
17
        gc "gopkg.in/check.v1"
 
18
        "gopkg.in/errgo.v1"
 
19
        "gopkg.in/juju/charm.v6-unstable"
 
20
        "gopkg.in/juju/charm.v6-unstable/resource"
 
21
        "gopkg.in/yaml.v2"
 
22
)
 
23
 
 
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.
 
28
//
 
29
// All methods on Charm may be called concurrently.
 
30
type Charm struct {
 
31
        meta     *charm.Meta
 
32
        config   *charm.Config
 
33
        actions  *charm.Actions
 
34
        metrics  *charm.Metrics
 
35
        revision int
 
36
 
 
37
        files filetesting.Entries
 
38
 
 
39
        makeArchiveOnce sync.Once
 
40
        archiveBytes    []byte
 
41
        archive         *charm.CharmArchive
 
42
}
 
43
 
 
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.
 
48
        Meta string
 
49
 
 
50
        // Config holds the contents of config.yaml.
 
51
        Config string
 
52
 
 
53
        // Actions holds the contents of actions.yaml.
 
54
        Actions string
 
55
 
 
56
        // Metrics holds the contents of metrics.yaml.
 
57
        Metrics string
 
58
 
 
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
 
63
 
 
64
        // Revision specifies the revision of the charm.
 
65
        Revision int
 
66
}
 
67
 
 
68
type file struct {
 
69
        path string
 
70
        data []byte
 
71
        perm os.FileMode
 
72
}
 
73
 
 
74
// NewCharm returns a charm following the given specification.
 
75
func NewCharm(c *gc.C, spec CharmSpec) *Charm {
 
76
        return newCharm(spec)
 
77
}
 
78
 
 
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 {
 
82
        ch := &Charm{
 
83
                revision: spec.Revision,
 
84
        }
 
85
        var err error
 
86
        ch.meta, err = charm.ReadMeta(strings.NewReader(spec.Meta))
 
87
        if err != nil {
 
88
                panic(err)
 
89
        }
 
90
 
 
91
        ch.files = append(ch.files, filetesting.File{
 
92
                Path: "metadata.yaml",
 
93
                Data: spec.Meta,
 
94
                Perm: 0644,
 
95
        })
 
96
 
 
97
        if spec.Config != "" {
 
98
                ch.config, err = charm.ReadConfig(strings.NewReader(spec.Config))
 
99
                if err != nil {
 
100
                        panic(err)
 
101
                }
 
102
                ch.files = append(ch.files, filetesting.File{
 
103
                        Path: "config.yaml",
 
104
                        Data: spec.Config,
 
105
                        Perm: 0644,
 
106
                })
 
107
        }
 
108
        if spec.Actions != "" {
 
109
                ch.actions, err = charm.ReadActionsYaml(strings.NewReader(spec.Actions))
 
110
                if err != nil {
 
111
                        panic(err)
 
112
                }
 
113
                ch.files = append(ch.files, filetesting.File{
 
114
                        Path: "actions.yaml",
 
115
                        Data: spec.Actions,
 
116
                        Perm: 0644,
 
117
                })
 
118
        }
 
119
        if spec.Metrics != "" {
 
120
                ch.metrics, err = charm.ReadMetrics(strings.NewReader(spec.Metrics))
 
121
                if err != nil {
 
122
                        panic(err)
 
123
                }
 
124
                ch.files = append(ch.files, filetesting.File{
 
125
                        Path: "metrics.yaml",
 
126
                        Data: spec.Metrics,
 
127
                        Perm: 0644,
 
128
                })
 
129
        }
 
130
        if spec.Files == nil {
 
131
                ch.files = append(ch.files, filetesting.File{
 
132
                        Path: "hooks/install",
 
133
                        Data: "#!/bin/sh\n",
 
134
                        Perm: 0755,
 
135
                }, filetesting.File{
 
136
                        Path: "hooks/start",
 
137
                        Data: "#!/bin/sh\n",
 
138
                        Perm: 0755,
 
139
                })
 
140
        } else {
 
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())
 
146
                        if names[name] {
 
147
                                panic(fmt.Errorf("duplicate file entry %q", f.GetPath()))
 
148
                        }
 
149
                        names[name] = true
 
150
                }
 
151
        }
 
152
        return ch
 
153
}
 
154
 
 
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 {
 
159
        if meta == nil {
 
160
                meta = new(charm.Meta)
 
161
        }
 
162
        metaYAML, err := yaml.Marshal(meta)
 
163
        if err != nil {
 
164
                panic(err)
 
165
        }
 
166
        return newCharm(CharmSpec{
 
167
                Meta: string(metaYAML),
 
168
        })
 
169
}
 
170
 
 
171
// Meta implements charm.Charm.Meta.
 
172
func (ch *Charm) Meta() *charm.Meta {
 
173
        return ch.meta
 
174
}
 
175
 
 
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{},
 
181
                }
 
182
        }
 
183
        return ch.config
 
184
}
 
185
 
 
186
// Metrics implements charm.Charm.Metrics.
 
187
func (ch *Charm) Metrics() *charm.Metrics {
 
188
        return ch.metrics
 
189
}
 
190
 
 
191
// Actions implements charm.Charm.Actions.
 
192
func (ch *Charm) Actions() *charm.Actions {
 
193
        if ch.actions == nil {
 
194
                return &charm.Actions{}
 
195
        }
 
196
        return ch.actions
 
197
}
 
198
 
 
199
// Revision implements charm.Charm.Revision.
 
200
func (ch *Charm) Revision() int {
 
201
        return ch.revision
 
202
}
 
203
 
 
204
// Archive returns a charm archive holding the charm.
 
205
func (ch *Charm) Archive() *charm.CharmArchive {
 
206
        ch.makeArchiveOnce.Do(ch.makeArchive)
 
207
        return ch.archive
 
208
}
 
209
 
 
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
 
215
}
 
216
 
 
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())
 
222
        return err
 
223
}
 
224
 
 
225
// Size returns the size of the charm's archive blob.
 
226
func (c *Charm) Size() int64 {
 
227
        return int64(len(c.ArchiveBytes()))
 
228
}
 
229
 
 
230
func (ch *Charm) makeArchive() {
 
231
        var buf bytes.Buffer
 
232
        zw := zip.NewWriter(&buf)
 
233
 
 
234
        for _, f := range ch.files {
 
235
                addZipEntry(zw, f)
 
236
        }
 
237
        if err := zw.Close(); err != nil {
 
238
                panic(err)
 
239
        }
 
240
        // ReadCharmArchiveFromReader requires a ReaderAt, so make one.
 
241
        r := bytes.NewReader(buf.Bytes())
 
242
 
 
243
        // Actually make the charm archive.
 
244
        archive, err := charm.ReadCharmArchiveFromReader(r, int64(buf.Len()))
 
245
        if err != nil {
 
246
                panic(err)
 
247
        }
 
248
        ch.archiveBytes = buf.Bytes()
 
249
        ch.archive = archive
 
250
        ch.archive.SetRevision(ch.revision)
 
251
}
 
252
 
 
253
func addZipEntry(zw *zip.Writer, f filetesting.Entry) {
 
254
        h := &zip.FileHeader{
 
255
                Name: f.GetPath(),
 
256
                // Don't bother compressing - the contents are so small that
 
257
                // it will just slow things down for no particular benefit.
 
258
                Method: zip.Store,
 
259
        }
 
260
        contents := ""
 
261
        switch f := f.(type) {
 
262
        case filetesting.Dir:
 
263
                h.SetMode(os.ModeDir | 0755)
 
264
        case filetesting.File:
 
265
                h.SetMode(f.Perm)
 
266
                contents = f.Data
 
267
        case filetesting.Symlink:
 
268
                h.SetMode(os.ModeSymlink | 0777)
 
269
                contents = f.Link
 
270
        }
 
271
        w, err := zw.CreateHeader(h)
 
272
        if err != nil {
 
273
                panic(err)
 
274
        }
 
275
        if contents != "" {
 
276
                if _, err := w.Write([]byte(contents)); err != nil {
 
277
                        panic(err)
 
278
                }
 
279
        }
 
280
}
 
281
 
 
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 {
 
286
        if m == nil {
 
287
                m = new(charm.Meta)
 
288
        }
 
289
        m.Series = series
 
290
        return m
 
291
}
 
292
 
 
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
 
296
// triple:
 
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.
 
301
//
 
302
// If m is nil, new(charm.Meta) will be used instead.
 
303
func MetaWithRelations(m *charm.Meta, relations ...string) *charm.Meta {
 
304
        if m == nil {
 
305
                m = new(charm.Meta)
 
306
        }
 
307
        provides := make(map[string]charm.Relation)
 
308
        requires := make(map[string]charm.Relation)
 
309
        for _, rel := range relations {
 
310
                r, err := parseRelation(rel)
 
311
                if err != nil {
 
312
                        panic(fmt.Errorf("bad relation %q", err))
 
313
                }
 
314
                if r.Role == charm.RoleProvider {
 
315
                        provides[r.Name] = r
 
316
                } else {
 
317
                        requires[r.Name] = r
 
318
                }
 
319
        }
 
320
        m.Provides = provides
 
321
        m.Requires = requires
 
322
        return m
 
323
}
 
324
 
 
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")
 
329
        }
 
330
        r := charm.Relation{
 
331
                Scope:     charm.ScopeGlobal,
 
332
                Name:      fields[1],
 
333
                Interface: fields[2],
 
334
        }
 
335
        switch fields[0] {
 
336
        case "provides":
 
337
                r.Role = charm.RoleProvider
 
338
        case "requires":
 
339
                r.Role = charm.RoleRequirer
 
340
        default:
 
341
                return charm.Relation{}, errgo.Newf("unknown role")
 
342
        }
 
343
        return r, nil
 
344
}
 
345
 
 
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
 
348
// instead.
 
349
//
 
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 {
 
354
        if m == nil {
 
355
                m = new(charm.Meta)
 
356
        }
 
357
        m.Resources = make(map[string]resource.Meta)
 
358
        for _, name := range resources {
 
359
                m.Resources[name] = resource.Meta{
 
360
                        Name:        name,
 
361
                        Type:        resource.TypeFile,
 
362
                        Path:        name + "-file",
 
363
                        Description: name + " description",
 
364
                }
 
365
        }
 
366
        return m
 
367
}