github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/testing/charm.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "fmt" 8 "go/build" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "sync" 13 14 "launchpad.net/juju-core/charm" 15 ) 16 17 func check(err error) { 18 if err != nil { 19 panic(err) 20 } 21 } 22 23 // Repo represents a charm repository used for testing. 24 type Repo struct { 25 once sync.Once 26 path string 27 } 28 29 func (r *Repo) Path() string { 30 r.once.Do(r.init) 31 return r.path 32 } 33 34 // init is called once when r.Path() is called for the first time, and 35 // it initializes r.path to the location of the local testing 36 // repository. 37 func (r *Repo) init() { 38 p, err := build.Import("launchpad.net/juju-core/testing", "", build.FindOnly) 39 check(err) 40 r.path = filepath.Join(p.Dir, "repo") 41 } 42 43 // Charms represents the specific charm repository stored in this package and 44 // used by the Juju unit tests. The series name is "quantal". 45 var Charms = &Repo{} 46 47 func clone(dst, src string) string { 48 check(exec.Command("cp", "-r", src, dst).Run()) 49 return filepath.Join(dst, filepath.Base(src)) 50 } 51 52 // DirPath returns the path to a charm directory with the given name in the 53 // default series 54 func (r *Repo) DirPath(name string) string { 55 return filepath.Join(r.Path(), "quantal", name) 56 } 57 58 // Dir returns the actual charm.Dir named name. 59 func (r *Repo) Dir(name string) *charm.Dir { 60 ch, err := charm.ReadDir(r.DirPath(name)) 61 check(err) 62 return ch 63 } 64 65 // ClonedDirPath returns the path to a new copy of the default charm directory 66 // named name. 67 func (r *Repo) ClonedDirPath(dst, name string) string { 68 return clone(dst, r.DirPath(name)) 69 } 70 71 // RenamedClonedDirPath returns the path to a new copy of the default 72 // charm directory named name, but renames it to newName. 73 func (r *Repo) RenamedClonedDirPath(dst, name, newName string) string { 74 newDst := clone(dst, r.DirPath(name)) 75 renamedDst := filepath.Join(filepath.Dir(newDst), newName) 76 check(os.Rename(newDst, renamedDst)) 77 return renamedDst 78 } 79 80 // ClonedDir returns an actual charm.Dir based on a new copy of the charm directory 81 // named name, in the directory dst. 82 func (r *Repo) ClonedDir(dst, name string) *charm.Dir { 83 ch, err := charm.ReadDir(r.ClonedDirPath(dst, name)) 84 check(err) 85 return ch 86 } 87 88 // ClonedURL makes a copy of the charm directory. It will create a directory 89 // with the series name if it does not exist, and then clone the charm named 90 // name into that directory. The return value is a URL pointing at the local 91 // charm. 92 func (r *Repo) ClonedURL(dst, series, name string) *charm.URL { 93 dst = filepath.Join(dst, series) 94 if err := os.MkdirAll(dst, os.FileMode(0777)); err != nil { 95 panic(fmt.Errorf("cannot make destination directory: %v", err)) 96 } 97 clone(dst, r.DirPath(name)) 98 return &charm.URL{ 99 Schema: "local", 100 Series: series, 101 Name: name, 102 Revision: -1, 103 } 104 } 105 106 // BundlePath returns the path to a new charm bundle file created from the 107 // charm directory named name, in the directory dst. 108 func (r *Repo) BundlePath(dst, name string) string { 109 dir := r.Dir(name) 110 path := filepath.Join(dst, "bundle.charm") 111 file, err := os.Create(path) 112 check(err) 113 defer file.Close() 114 check(dir.BundleTo(file)) 115 return path 116 } 117 118 // Bundle returns an actual charm.Bundle created from a new charm bundle file 119 // created from the charm directory named name, in the directory dst. 120 func (r *Repo) Bundle(dst, name string) *charm.Bundle { 121 ch, err := charm.ReadBundle(r.BundlePath(dst, name)) 122 check(err) 123 return ch 124 } 125 126 // MockCharmStore implements charm.Repository and is used to isolate tests 127 // that would otherwise need to hit the real charm store. 128 type MockCharmStore struct { 129 charms map[string]map[int]*charm.Bundle 130 AuthAttrs string 131 TestMode bool 132 } 133 134 func NewMockCharmStore() *MockCharmStore { 135 return &MockCharmStore{charms: map[string]map[int]*charm.Bundle{}} 136 } 137 138 func (s *MockCharmStore) WithAuthAttrs(auth string) charm.Repository { 139 s.AuthAttrs = auth 140 return s 141 } 142 143 func (s *MockCharmStore) WithTestMode(testMode bool) charm.Repository { 144 s.TestMode = testMode 145 return s 146 } 147 148 // SetCharm adds and removes charms in s. The affected charm is identified by 149 // charmURL, which must be revisioned. If bundle is nil, the charm will be 150 // removed; otherwise, it will be stored. It is an error to store a bundle 151 // under a charmURL that does not share its name and revision. 152 func (s *MockCharmStore) SetCharm(charmURL *charm.URL, bundle *charm.Bundle) error { 153 base := charmURL.WithRevision(-1).String() 154 if charmURL.Revision < 0 { 155 return fmt.Errorf("bad charm url revision") 156 } 157 if bundle == nil { 158 delete(s.charms[base], charmURL.Revision) 159 return nil 160 } 161 bundleRev := bundle.Revision() 162 bundleName := bundle.Meta().Name 163 if bundleName != charmURL.Name || bundleRev != charmURL.Revision { 164 return fmt.Errorf("charm url %s mismatch with bundle %s-%d", charmURL, bundleName, bundleRev) 165 } 166 if _, found := s.charms[base]; !found { 167 s.charms[base] = map[int]*charm.Bundle{} 168 } 169 s.charms[base][charmURL.Revision] = bundle 170 return nil 171 } 172 173 // interpret extracts from charmURL information relevant to both Latest and 174 // Get. The returned "base" is always the string representation of the 175 // unrevisioned part of charmURL; the "rev" wil be taken from the charmURL if 176 // available, and will otherwise be the revision of the latest charm in the 177 // store with the same "base". 178 func (s *MockCharmStore) interpret(charmURL *charm.URL) (base string, rev int) { 179 base, rev = charmURL.WithRevision(-1).String(), charmURL.Revision 180 if rev == -1 { 181 for candidate := range s.charms[base] { 182 if candidate > rev { 183 rev = candidate 184 } 185 } 186 } 187 return 188 } 189 190 // Get implements charm.Repository.Get. 191 func (s *MockCharmStore) Get(charmURL *charm.URL) (charm.Charm, error) { 192 base, rev := s.interpret(charmURL) 193 charm, found := s.charms[base][rev] 194 if !found { 195 return nil, fmt.Errorf("charm not found in mock store: %s", charmURL) 196 } 197 return charm, nil 198 } 199 200 // Latest implements charm.Repository.Latest. 201 func (s *MockCharmStore) Latest(charmURLs ...*charm.URL) ([]charm.CharmRevision, error) { 202 result := make([]charm.CharmRevision, len(charmURLs)) 203 for i, curl := range charmURLs { 204 charmURL := curl.WithRevision(-1) 205 base, rev := s.interpret(charmURL) 206 if _, found := s.charms[base][rev]; !found { 207 result[i].Err = fmt.Errorf("charm not found in mock store: %s", charmURL) 208 } else { 209 result[i].Revision = rev 210 } 211 } 212 return result, nil 213 }