github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/charm/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  	"github.com/juju/juju/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("github.com/juju/juju/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("/bin/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  		Reference: charm.Reference{
   100  			Schema:   "local",
   101  			Name:     name,
   102  			Revision: -1,
   103  		},
   104  		Series: series,
   105  	}
   106  }
   107  
   108  // BundlePath returns the path to a new charm bundle file created from the
   109  // charm directory named name, in the directory dst.
   110  func (r *Repo) BundlePath(dst, name string) string {
   111  	dir := r.Dir(name)
   112  	path := filepath.Join(dst, "bundle.charm")
   113  	file, err := os.Create(path)
   114  	check(err)
   115  	defer file.Close()
   116  	check(dir.BundleTo(file))
   117  	return path
   118  }
   119  
   120  // Bundle returns an actual charm.Bundle created from a new charm bundle file
   121  // created from the charm directory named name, in the directory dst.
   122  func (r *Repo) Bundle(dst, name string) *charm.Bundle {
   123  	ch, err := charm.ReadBundle(r.BundlePath(dst, name))
   124  	check(err)
   125  	return ch
   126  }
   127  
   128  // MockCharmStore implements charm.Repository and is used to isolate tests
   129  // that would otherwise need to hit the real charm store.
   130  type MockCharmStore struct {
   131  	charms        map[string]map[int]*charm.Bundle
   132  	AuthAttrs     string
   133  	TestMode      bool
   134  	DefaultSeries string
   135  }
   136  
   137  func NewMockCharmStore() *MockCharmStore {
   138  	return &MockCharmStore{charms: map[string]map[int]*charm.Bundle{}}
   139  }
   140  
   141  func (s *MockCharmStore) WithAuthAttrs(auth string) charm.Repository {
   142  	s.AuthAttrs = auth
   143  	return s
   144  }
   145  
   146  func (s *MockCharmStore) WithTestMode(testMode bool) charm.Repository {
   147  	s.TestMode = testMode
   148  	return s
   149  }
   150  
   151  func (s *MockCharmStore) WithDefaultSeries(series string) charm.Repository {
   152  	s.DefaultSeries = series
   153  	return s
   154  }
   155  
   156  func (s *MockCharmStore) Resolve(ref charm.Reference) (*charm.URL, error) {
   157  	if s.DefaultSeries == "" {
   158  		return nil, fmt.Errorf("missing default series, cannot resolve charm url: %q", ref)
   159  	}
   160  	return &charm.URL{Reference: ref, Series: s.DefaultSeries}, nil
   161  }
   162  
   163  // SetCharm adds and removes charms in s. The affected charm is identified by
   164  // charmURL, which must be revisioned. If bundle is nil, the charm will be
   165  // removed; otherwise, it will be stored. It is an error to store a bundle
   166  // under a charmURL that does not share its name and revision.
   167  func (s *MockCharmStore) SetCharm(charmURL *charm.URL, bundle *charm.Bundle) error {
   168  	base := charmURL.WithRevision(-1).String()
   169  	if charmURL.Revision < 0 {
   170  		return fmt.Errorf("bad charm url revision")
   171  	}
   172  	if bundle == nil {
   173  		delete(s.charms[base], charmURL.Revision)
   174  		return nil
   175  	}
   176  	bundleRev := bundle.Revision()
   177  	bundleName := bundle.Meta().Name
   178  	if bundleName != charmURL.Name || bundleRev != charmURL.Revision {
   179  		return fmt.Errorf("charm url %s mismatch with bundle %s-%d", charmURL, bundleName, bundleRev)
   180  	}
   181  	if _, found := s.charms[base]; !found {
   182  		s.charms[base] = map[int]*charm.Bundle{}
   183  	}
   184  	s.charms[base][charmURL.Revision] = bundle
   185  	return nil
   186  }
   187  
   188  // interpret extracts from charmURL information relevant to both Latest and
   189  // Get. The returned "base" is always the string representation of the
   190  // unrevisioned part of charmURL; the "rev" wil be taken from the charmURL if
   191  // available, and will otherwise be the revision of the latest charm in the
   192  // store with the same "base".
   193  func (s *MockCharmStore) interpret(charmURL *charm.URL) (base string, rev int) {
   194  	base, rev = charmURL.WithRevision(-1).String(), charmURL.Revision
   195  	if rev == -1 {
   196  		for candidate := range s.charms[base] {
   197  			if candidate > rev {
   198  				rev = candidate
   199  			}
   200  		}
   201  	}
   202  	return
   203  }
   204  
   205  // Get implements charm.Repository.Get.
   206  func (s *MockCharmStore) Get(charmURL *charm.URL) (charm.Charm, error) {
   207  	base, rev := s.interpret(charmURL)
   208  	charm, found := s.charms[base][rev]
   209  	if !found {
   210  		return nil, fmt.Errorf("charm not found in mock store: %s", charmURL)
   211  	}
   212  	return charm, nil
   213  }
   214  
   215  // Latest implements charm.Repository.Latest.
   216  func (s *MockCharmStore) Latest(charmURLs ...*charm.URL) ([]charm.CharmRevision, error) {
   217  	result := make([]charm.CharmRevision, len(charmURLs))
   218  	for i, curl := range charmURLs {
   219  		charmURL := curl.WithRevision(-1)
   220  		base, rev := s.interpret(charmURL)
   221  		if _, found := s.charms[base][rev]; !found {
   222  			result[i].Err = fmt.Errorf("charm not found in mock store: %s", charmURL)
   223  		} else {
   224  			result[i].Revision = rev
   225  		}
   226  	}
   227  	return result, nil
   228  }