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  }