github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/cmd/juju/publish_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  
    10  	gc "launchpad.net/gocheck"
    11  
    12  	"launchpad.net/juju-core/bzr"
    13  	"launchpad.net/juju-core/charm"
    14  	"launchpad.net/juju-core/cmd"
    15  	"launchpad.net/juju-core/testing"
    16  	"launchpad.net/juju-core/testing/testbase"
    17  )
    18  
    19  // Sadly, this is a very slow test suite, heavily dominated by calls to bzr.
    20  
    21  type PublishSuite struct {
    22  	testbase.LoggingSuite
    23  	testing.HTTPSuite
    24  
    25  	home       *testing.FakeHome
    26  	dir        string
    27  	oldBaseURL string
    28  	branch     *bzr.Branch
    29  }
    30  
    31  var _ = gc.Suite(&PublishSuite{})
    32  
    33  func touch(c *gc.C, filename string) {
    34  	f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0644)
    35  	c.Assert(err, gc.IsNil)
    36  	f.Close()
    37  }
    38  
    39  func addMeta(c *gc.C, branch *bzr.Branch, meta string) {
    40  	if meta == "" {
    41  		meta = "name: wordpress\nsummary: Some summary\ndescription: Some description.\n"
    42  	}
    43  	f, err := os.Create(branch.Join("metadata.yaml"))
    44  	c.Assert(err, gc.IsNil)
    45  	_, err = f.Write([]byte(meta))
    46  	f.Close()
    47  	c.Assert(err, gc.IsNil)
    48  	err = branch.Add("metadata.yaml")
    49  	c.Assert(err, gc.IsNil)
    50  	err = branch.Commit("Added metadata.yaml.")
    51  	c.Assert(err, gc.IsNil)
    52  }
    53  
    54  func (s *PublishSuite) runPublish(c *gc.C, args ...string) (*cmd.Context, error) {
    55  	return testing.RunCommandInDir(c, &PublishCommand{}, args, s.dir)
    56  }
    57  
    58  const pollDelay = testing.ShortWait
    59  
    60  func (s *PublishSuite) SetUpSuite(c *gc.C) {
    61  	s.LoggingSuite.SetUpSuite(c)
    62  	s.HTTPSuite.SetUpSuite(c)
    63  
    64  	s.oldBaseURL = charm.Store.BaseURL
    65  	charm.Store.BaseURL = s.URL("")
    66  }
    67  
    68  func (s *PublishSuite) TearDownSuite(c *gc.C) {
    69  	s.LoggingSuite.TearDownSuite(c)
    70  	s.HTTPSuite.TearDownSuite(c)
    71  
    72  	charm.Store.BaseURL = s.oldBaseURL
    73  }
    74  
    75  func (s *PublishSuite) SetUpTest(c *gc.C) {
    76  	s.LoggingSuite.SetUpTest(c)
    77  	s.HTTPSuite.SetUpTest(c)
    78  	s.home = testing.MakeFakeHomeWithFiles(c, []testing.TestFile{
    79  		{
    80  			Name: ".bazaar/bazaar.conf",
    81  			Data: "[DEFAULT]\nemail = Test <testing@testing.invalid>\n",
    82  		},
    83  	})
    84  
    85  	s.dir = c.MkDir()
    86  	s.branch = bzr.New(s.dir)
    87  	err := s.branch.Init()
    88  	c.Assert(err, gc.IsNil)
    89  }
    90  
    91  func (s *PublishSuite) TearDownTest(c *gc.C) {
    92  	s.home.Restore()
    93  	s.HTTPSuite.TearDownTest(c)
    94  	s.LoggingSuite.TearDownTest(c)
    95  }
    96  
    97  func (s *PublishSuite) TestNoBranch(c *gc.C) {
    98  	dir := c.MkDir()
    99  	_, err := testing.RunCommandInDir(c, &PublishCommand{}, []string{"cs:precise/wordpress"}, dir)
   100  	c.Assert(err, gc.ErrorMatches, fmt.Sprintf("not a charm branch: %s", dir))
   101  }
   102  
   103  func (s *PublishSuite) TestEmpty(c *gc.C) {
   104  	_, err := s.runPublish(c, "cs:precise/wordpress")
   105  	c.Assert(err, gc.ErrorMatches, `cannot obtain local digest: branch has no content`)
   106  }
   107  
   108  func (s *PublishSuite) TestFrom(c *gc.C) {
   109  	_, err := testing.RunCommandInDir(c, &PublishCommand{}, []string{"--from", s.dir, "cs:precise/wordpress"}, c.MkDir())
   110  	c.Assert(err, gc.ErrorMatches, `cannot obtain local digest: branch has no content`)
   111  }
   112  
   113  func (s *PublishSuite) TestMissingSeries(c *gc.C) {
   114  	_, err := s.runPublish(c, "cs:wordpress")
   115  	c.Assert(err, gc.ErrorMatches, `cannot infer charm URL for "cs:wordpress": no series provided`)
   116  }
   117  
   118  func (s *PublishSuite) TestNotClean(c *gc.C) {
   119  	touch(c, s.branch.Join("file"))
   120  	_, err := s.runPublish(c, "cs:precise/wordpress")
   121  	c.Assert(err, gc.ErrorMatches, `branch is not clean \(bzr status\)`)
   122  }
   123  
   124  func (s *PublishSuite) TestNoPushLocation(c *gc.C) {
   125  	addMeta(c, s.branch, "")
   126  	_, err := s.runPublish(c)
   127  	c.Assert(err, gc.ErrorMatches, `no charm URL provided and cannot infer from current directory \(no push location\)`)
   128  }
   129  
   130  func (s *PublishSuite) TestUnknownPushLocation(c *gc.C) {
   131  	addMeta(c, s.branch, "")
   132  	err := s.branch.Push(&bzr.PushAttr{Location: c.MkDir() + "/foo", Remember: true})
   133  	c.Assert(err, gc.IsNil)
   134  	_, err = s.runPublish(c)
   135  	c.Assert(err, gc.ErrorMatches, `cannot infer charm URL from branch location: ".*/foo"`)
   136  }
   137  
   138  func (s *PublishSuite) TestWrongRepository(c *gc.C) {
   139  	addMeta(c, s.branch, "")
   140  	_, err := s.runPublish(c, "local:precise/wordpress")
   141  	c.Assert(err, gc.ErrorMatches, "charm URL must reference the juju charm store")
   142  }
   143  
   144  func (s *PublishSuite) TestInferURL(c *gc.C) {
   145  	addMeta(c, s.branch, "")
   146  
   147  	cmd := &PublishCommand{}
   148  	cmd.ChangePushLocation(func(location string) string {
   149  		c.Assert(location, gc.Equals, "lp:charms/precise/wordpress")
   150  		c.SucceedNow()
   151  		panic("unreachable")
   152  	})
   153  
   154  	_, err := testing.RunCommandInDir(c, cmd, []string{"precise/wordpress"}, s.dir)
   155  	c.Assert(err, gc.IsNil)
   156  	c.Fatal("shouldn't get here; location closure didn't run?")
   157  }
   158  
   159  func (s *PublishSuite) TestBrokenCharm(c *gc.C) {
   160  	addMeta(c, s.branch, "name: wordpress\nsummary: Some summary\n")
   161  	_, err := s.runPublish(c, "cs:precise/wordpress")
   162  	c.Assert(err, gc.ErrorMatches, "metadata: description: expected string, got nothing")
   163  }
   164  
   165  func (s *PublishSuite) TestWrongName(c *gc.C) {
   166  	addMeta(c, s.branch, "")
   167  	_, err := s.runPublish(c, "cs:precise/mysql")
   168  	c.Assert(err, gc.ErrorMatches, `charm name in metadata must match name in URL: "wordpress" != "mysql"`)
   169  }
   170  
   171  func (s *PublishSuite) TestPreExistingPublished(c *gc.C) {
   172  	addMeta(c, s.branch, "")
   173  
   174  	// Pretend the store has seen the digest before, and it has succeeded.
   175  	digest, err := s.branch.RevisionId()
   176  	c.Assert(err, gc.IsNil)
   177  	body := `{"cs:precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}`
   178  	testing.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   179  
   180  	ctx, err := s.runPublish(c, "cs:precise/wordpress")
   181  	c.Assert(err, gc.IsNil)
   182  	c.Assert(testing.Stdout(ctx), gc.Equals, "cs:precise/wordpress-42\n")
   183  
   184  	req := testing.Server.WaitRequest()
   185  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   186  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress@"+digest)
   187  }
   188  
   189  func (s *PublishSuite) TestPreExistingPublishedEdge(c *gc.C) {
   190  	addMeta(c, s.branch, "")
   191  
   192  	// If it doesn't find the right digest on the first try, it asks again for
   193  	// any digest at all to keep the tip in mind. There's a small chance that
   194  	// on the second request the tip has changed and matches the digest we're
   195  	// looking for, in which case we have the answer already.
   196  	digest, err := s.branch.RevisionId()
   197  	c.Assert(err, gc.IsNil)
   198  	var body string
   199  	body = `{"cs:precise/wordpress": {"errors": ["entry not found"]}}`
   200  	testing.Server.Response(200, nil, []byte(body))
   201  	body = `{"cs:precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}`
   202  	testing.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   203  
   204  	ctx, err := s.runPublish(c, "cs:precise/wordpress")
   205  	c.Assert(err, gc.IsNil)
   206  	c.Assert(testing.Stdout(ctx), gc.Equals, "cs:precise/wordpress-42\n")
   207  
   208  	req := testing.Server.WaitRequest()
   209  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   210  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress@"+digest)
   211  
   212  	req = testing.Server.WaitRequest()
   213  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   214  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress")
   215  }
   216  
   217  func (s *PublishSuite) TestPreExistingPublishError(c *gc.C) {
   218  	addMeta(c, s.branch, "")
   219  
   220  	// Pretend the store has seen the digest before, and it has failed.
   221  	digest, err := s.branch.RevisionId()
   222  	c.Assert(err, gc.IsNil)
   223  	body := `{"cs:precise/wordpress": {"kind": "publish-error", "digest": %q, "errors": ["an error"]}}`
   224  	testing.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   225  
   226  	_, err = s.runPublish(c, "cs:precise/wordpress")
   227  	c.Assert(err, gc.ErrorMatches, "charm could not be published: an error")
   228  
   229  	req := testing.Server.WaitRequest()
   230  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   231  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress@"+digest)
   232  }
   233  
   234  func (s *PublishSuite) TestFullPublish(c *gc.C) {
   235  	addMeta(c, s.branch, "")
   236  
   237  	digest, err := s.branch.RevisionId()
   238  	c.Assert(err, gc.IsNil)
   239  
   240  	pushBranch := bzr.New(c.MkDir())
   241  	err = pushBranch.Init()
   242  	c.Assert(err, gc.IsNil)
   243  
   244  	cmd := &PublishCommand{}
   245  	cmd.ChangePushLocation(func(location string) string {
   246  		c.Assert(location, gc.Equals, "lp:~user/charms/precise/wordpress/trunk")
   247  		return pushBranch.Location()
   248  	})
   249  	cmd.SetPollDelay(testing.ShortWait)
   250  
   251  	var body string
   252  
   253  	// The local digest isn't found.
   254  	body = `{"cs:~user/precise/wordpress": {"kind": "", "errors": ["entry not found"]}}`
   255  	testing.Server.Response(200, nil, []byte(body))
   256  
   257  	// But the charm exists with an arbitrary non-matching digest.
   258  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": "other-digest"}}`
   259  	testing.Server.Response(200, nil, []byte(body))
   260  
   261  	// After the branch is pushed we fake the publishing delay.
   262  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": "other-digest"}}`
   263  	testing.Server.Response(200, nil, []byte(body))
   264  
   265  	// And finally report success.
   266  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}`
   267  	testing.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   268  
   269  	ctx, err := testing.RunCommandInDir(c, cmd, []string{"cs:~user/precise/wordpress"}, s.dir)
   270  	c.Assert(err, gc.IsNil)
   271  	c.Assert(testing.Stdout(ctx), gc.Equals, "cs:~user/precise/wordpress-42\n")
   272  
   273  	// Ensure the branch was actually pushed.
   274  	pushDigest, err := pushBranch.RevisionId()
   275  	c.Assert(err, gc.IsNil)
   276  	c.Assert(pushDigest, gc.Equals, digest)
   277  
   278  	// And that all the requests were sent with the proper data.
   279  	req := testing.Server.WaitRequest()
   280  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   281  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress@"+digest)
   282  
   283  	for i := 0; i < 3; i++ {
   284  		// The second request grabs tip to see the current state, and the
   285  		// following requests are done after pushing to see when it changes.
   286  		req = testing.Server.WaitRequest()
   287  		c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   288  		c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress")
   289  	}
   290  }
   291  
   292  func (s *PublishSuite) TestFullPublishError(c *gc.C) {
   293  	addMeta(c, s.branch, "")
   294  
   295  	digest, err := s.branch.RevisionId()
   296  	c.Assert(err, gc.IsNil)
   297  
   298  	pushBranch := bzr.New(c.MkDir())
   299  	err = pushBranch.Init()
   300  	c.Assert(err, gc.IsNil)
   301  
   302  	cmd := &PublishCommand{}
   303  	cmd.ChangePushLocation(func(location string) string {
   304  		c.Assert(location, gc.Equals, "lp:~user/charms/precise/wordpress/trunk")
   305  		return pushBranch.Location()
   306  	})
   307  	cmd.SetPollDelay(pollDelay)
   308  
   309  	var body string
   310  
   311  	// The local digest isn't found.
   312  	body = `{"cs:~user/precise/wordpress": {"kind": "", "errors": ["entry not found"]}}`
   313  	testing.Server.Response(200, nil, []byte(body))
   314  
   315  	// And tip isn't found either, meaning the charm was never published.
   316  	testing.Server.Response(200, nil, []byte(body))
   317  
   318  	// After the branch is pushed we fake the publishing delay.
   319  	testing.Server.Response(200, nil, []byte(body))
   320  
   321  	// And finally report success.
   322  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}`
   323  	testing.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest)))
   324  
   325  	ctx, err := testing.RunCommandInDir(c, cmd, []string{"cs:~user/precise/wordpress"}, s.dir)
   326  	c.Assert(err, gc.IsNil)
   327  	c.Assert(testing.Stdout(ctx), gc.Equals, "cs:~user/precise/wordpress-42\n")
   328  
   329  	// Ensure the branch was actually pushed.
   330  	pushDigest, err := pushBranch.RevisionId()
   331  	c.Assert(err, gc.IsNil)
   332  	c.Assert(pushDigest, gc.Equals, digest)
   333  
   334  	// And that all the requests were sent with the proper data.
   335  	req := testing.Server.WaitRequest()
   336  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   337  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress@"+digest)
   338  
   339  	for i := 0; i < 3; i++ {
   340  		// The second request grabs tip to see the current state, and the
   341  		// following requests are done after pushing to see when it changes.
   342  		req = testing.Server.WaitRequest()
   343  		c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   344  		c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress")
   345  	}
   346  }
   347  
   348  func (s *PublishSuite) TestFullPublishRace(c *gc.C) {
   349  	addMeta(c, s.branch, "")
   350  
   351  	digest, err := s.branch.RevisionId()
   352  	c.Assert(err, gc.IsNil)
   353  
   354  	pushBranch := bzr.New(c.MkDir())
   355  	err = pushBranch.Init()
   356  	c.Assert(err, gc.IsNil)
   357  
   358  	cmd := &PublishCommand{}
   359  	cmd.ChangePushLocation(func(location string) string {
   360  		c.Assert(location, gc.Equals, "lp:~user/charms/precise/wordpress/trunk")
   361  		return pushBranch.Location()
   362  	})
   363  	cmd.SetPollDelay(pollDelay)
   364  
   365  	var body string
   366  
   367  	// The local digest isn't found.
   368  	body = `{"cs:~user/precise/wordpress": {"kind": "", "errors": ["entry not found"]}}`
   369  	testing.Server.Response(200, nil, []byte(body))
   370  
   371  	// And tip isn't found either, meaning the charm was never published.
   372  	testing.Server.Response(200, nil, []byte(body))
   373  
   374  	// After the branch is pushed we fake the publishing delay.
   375  	testing.Server.Response(200, nil, []byte(body))
   376  
   377  	// But, surprisingly, the digest changed to something else entirely.
   378  	body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": "surprising-digest", "revision": 42}}`
   379  	testing.Server.Response(200, nil, []byte(body))
   380  
   381  	_, err = testing.RunCommandInDir(c, cmd, []string{"cs:~user/precise/wordpress"}, s.dir)
   382  	c.Assert(err, gc.ErrorMatches, `charm changed but not to local charm digest; publishing race\?`)
   383  
   384  	// Ensure the branch was actually pushed.
   385  	pushDigest, err := pushBranch.RevisionId()
   386  	c.Assert(err, gc.IsNil)
   387  	c.Assert(pushDigest, gc.Equals, digest)
   388  
   389  	// And that all the requests were sent with the proper data.
   390  	req := testing.Server.WaitRequest()
   391  	c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   392  	c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress@"+digest)
   393  
   394  	for i := 0; i < 3; i++ {
   395  		// The second request grabs tip to see the current state, and the
   396  		// following requests are done after pushing to see when it changes.
   397  		req = testing.Server.WaitRequest()
   398  		c.Assert(req.URL.Path, gc.Equals, "/charm-event")
   399  		c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress")
   400  	}
   401  }