github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/commands/publish_test.go (about)

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