github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 gc "gopkg.in/check.v1" 15 "gopkg.in/juju/charm.v5/charmrepo" 16 17 "github.com/juju/juju/bzr" 18 "github.com/juju/juju/cmd/envcmd" 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.FakeJujuHomeSuite 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, envcmd.Wrap(&PublishCommand{}), args, s.dir) 58 } 59 60 const pollDelay = testing.ShortWait 61 62 func (s *PublishSuite) SetUpSuite(c *gc.C) { 63 s.FakeJujuHomeSuite.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.FakeJujuHomeSuite.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.FakeJujuHomeSuite.SetUpTest(c) 79 s.HTTPSuite.SetUpTest(c) 80 s.PatchEnvironment("BZR_HOME", utils.Home()) 81 s.FakeJujuHomeSuite.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.FakeJujuHomeSuite.TearDownTest(c) 95 } 96 97 func (s *PublishSuite) TestNoBranch(c *gc.C) { 98 dir := c.MkDir() 99 _, err := testing.RunCommandInDir(c, envcmd.Wrap(&PublishCommand{}), []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, envcmd.Wrap(&PublishCommand{}), []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) TestMissingSeries(c *gc.C) { 116 _, err := s.runPublish(c, "cs:wordpress") 117 c.Assert(err, gc.ErrorMatches, `cannot infer charm URL for "cs:wordpress": charm url series is not resolved`) 118 } 119 120 func (s *PublishSuite) TestNotClean(c *gc.C) { 121 touch(c, s.branch.Join("file")) 122 _, err := s.runPublish(c, "cs:precise/wordpress") 123 c.Assert(err, gc.ErrorMatches, `branch is not clean \(bzr status\)`) 124 } 125 126 func (s *PublishSuite) TestNoPushLocation(c *gc.C) { 127 addMeta(c, s.branch, "") 128 _, err := s.runPublish(c) 129 c.Assert(err, gc.ErrorMatches, `no charm URL provided and cannot infer from current directory \(no push location\)`) 130 } 131 132 func (s *PublishSuite) TestUnknownPushLocation(c *gc.C) { 133 addMeta(c, s.branch, "") 134 err := s.branch.Push(&bzr.PushAttr{Location: c.MkDir() + "/foo", Remember: true}) 135 c.Assert(err, jc.ErrorIsNil) 136 _, err = s.runPublish(c) 137 c.Assert(err, gc.ErrorMatches, `cannot infer charm URL from branch location: ".*/foo"`) 138 } 139 140 func (s *PublishSuite) TestWrongRepository(c *gc.C) { 141 addMeta(c, s.branch, "") 142 _, err := s.runPublish(c, "local:precise/wordpress") 143 c.Assert(err, gc.ErrorMatches, "charm URL must reference the juju charm store") 144 } 145 146 func (s *PublishSuite) TestInferURL(c *gc.C) { 147 addMeta(c, s.branch, "") 148 149 cmd := &PublishCommand{} 150 cmd.ChangePushLocation(func(location string) string { 151 c.Assert(location, gc.Equals, "lp:charms/precise/wordpress") 152 c.SucceedNow() 153 panic("unreachable") 154 }) 155 156 _, err := testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"precise/wordpress"}, s.dir) 157 c.Assert(err, jc.ErrorIsNil) 158 c.Fatal("shouldn't get here; location closure didn't run?") 159 } 160 161 func (s *PublishSuite) TestBrokenCharm(c *gc.C) { 162 addMeta(c, s.branch, "name: wordpress\nsummary: Some summary\n") 163 _, err := s.runPublish(c, "cs:precise/wordpress") 164 c.Assert(err, gc.ErrorMatches, "metadata: description: expected string, got nothing") 165 } 166 167 func (s *PublishSuite) TestWrongName(c *gc.C) { 168 addMeta(c, s.branch, "") 169 _, err := s.runPublish(c, "cs:precise/mysql") 170 c.Assert(err, gc.ErrorMatches, `charm name in metadata must match name in URL: "wordpress" != "mysql"`) 171 } 172 173 func (s *PublishSuite) TestPreExistingPublished(c *gc.C) { 174 addMeta(c, s.branch, "") 175 176 // Pretend the store has seen the digest before, and it has succeeded. 177 digest, err := s.branch.RevisionId() 178 c.Assert(err, jc.ErrorIsNil) 179 body := `{"cs:precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}` 180 gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest))) 181 182 ctx, err := s.runPublish(c, "cs:precise/wordpress") 183 c.Assert(err, jc.ErrorIsNil) 184 c.Assert(testing.Stdout(ctx), gc.Equals, "cs:precise/wordpress-42\n") 185 186 req := gitjujutesting.Server.WaitRequest() 187 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 188 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress@"+digest) 189 } 190 191 func (s *PublishSuite) TestPreExistingPublishedEdge(c *gc.C) { 192 addMeta(c, s.branch, "") 193 194 // If it doesn't find the right digest on the first try, it asks again for 195 // any digest at all to keep the tip in mind. There's a small chance that 196 // on the second request the tip has changed and matches the digest we're 197 // looking for, in which case we have the answer already. 198 digest, err := s.branch.RevisionId() 199 c.Assert(err, jc.ErrorIsNil) 200 var body string 201 body = `{"cs:precise/wordpress": {"errors": ["entry not found"]}}` 202 gitjujutesting.Server.Response(200, nil, []byte(body)) 203 body = `{"cs:precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}` 204 gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest))) 205 206 ctx, err := s.runPublish(c, "cs:precise/wordpress") 207 c.Assert(err, jc.ErrorIsNil) 208 c.Assert(testing.Stdout(ctx), gc.Equals, "cs:precise/wordpress-42\n") 209 210 req := gitjujutesting.Server.WaitRequest() 211 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 212 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress@"+digest) 213 214 req = gitjujutesting.Server.WaitRequest() 215 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 216 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress") 217 } 218 219 func (s *PublishSuite) TestPreExistingPublishError(c *gc.C) { 220 addMeta(c, s.branch, "") 221 222 // Pretend the store has seen the digest before, and it has failed. 223 digest, err := s.branch.RevisionId() 224 c.Assert(err, jc.ErrorIsNil) 225 body := `{"cs:precise/wordpress": {"kind": "publish-error", "digest": %q, "errors": ["an error"]}}` 226 gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest))) 227 228 _, err = s.runPublish(c, "cs:precise/wordpress") 229 c.Assert(err, gc.ErrorMatches, "charm could not be published: an error") 230 231 req := gitjujutesting.Server.WaitRequest() 232 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 233 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:precise/wordpress@"+digest) 234 } 235 236 func (s *PublishSuite) TestFullPublish(c *gc.C) { 237 addMeta(c, s.branch, "") 238 239 digest, err := s.branch.RevisionId() 240 c.Assert(err, jc.ErrorIsNil) 241 242 pushBranch := bzr.New(c.MkDir()) 243 err = pushBranch.Init() 244 c.Assert(err, jc.ErrorIsNil) 245 246 cmd := &PublishCommand{} 247 cmd.ChangePushLocation(func(location string) string { 248 c.Assert(location, gc.Equals, "lp:~user/charms/precise/wordpress/trunk") 249 return pushBranch.Location() 250 }) 251 cmd.SetPollDelay(testing.ShortWait) 252 253 var body string 254 255 // The local digest isn't found. 256 body = `{"cs:~user/precise/wordpress": {"kind": "", "errors": ["entry not found"]}}` 257 gitjujutesting.Server.Response(200, nil, []byte(body)) 258 259 // But the charm exists with an arbitrary non-matching digest. 260 body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": "other-digest"}}` 261 gitjujutesting.Server.Response(200, nil, []byte(body)) 262 263 // After the branch is pushed we fake the publishing delay. 264 body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": "other-digest"}}` 265 gitjujutesting.Server.Response(200, nil, []byte(body)) 266 267 // And finally report success. 268 body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}` 269 gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest))) 270 271 ctx, err := testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"cs:~user/precise/wordpress"}, s.dir) 272 c.Assert(err, jc.ErrorIsNil) 273 c.Assert(testing.Stdout(ctx), gc.Equals, "cs:~user/precise/wordpress-42\n") 274 275 // Ensure the branch was actually pushed. 276 pushDigest, err := pushBranch.RevisionId() 277 c.Assert(err, jc.ErrorIsNil) 278 c.Assert(pushDigest, gc.Equals, digest) 279 280 // And that all the requests were sent with the proper data. 281 req := gitjujutesting.Server.WaitRequest() 282 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 283 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress@"+digest) 284 285 for i := 0; i < 3; i++ { 286 // The second request grabs tip to see the current state, and the 287 // following requests are done after pushing to see when it changes. 288 req = gitjujutesting.Server.WaitRequest() 289 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 290 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress") 291 } 292 } 293 294 func (s *PublishSuite) TestFullPublishError(c *gc.C) { 295 addMeta(c, s.branch, "") 296 297 digest, err := s.branch.RevisionId() 298 c.Assert(err, jc.ErrorIsNil) 299 300 pushBranch := bzr.New(c.MkDir()) 301 err = pushBranch.Init() 302 c.Assert(err, jc.ErrorIsNil) 303 304 cmd := &PublishCommand{} 305 cmd.ChangePushLocation(func(location string) string { 306 c.Assert(location, gc.Equals, "lp:~user/charms/precise/wordpress/trunk") 307 return pushBranch.Location() 308 }) 309 cmd.SetPollDelay(pollDelay) 310 311 var body string 312 313 // The local digest isn't found. 314 body = `{"cs:~user/precise/wordpress": {"kind": "", "errors": ["entry not found"]}}` 315 gitjujutesting.Server.Response(200, nil, []byte(body)) 316 317 // And tip isn't found either, meaning the charm was never published. 318 gitjujutesting.Server.Response(200, nil, []byte(body)) 319 320 // After the branch is pushed we fake the publishing delay. 321 gitjujutesting.Server.Response(200, nil, []byte(body)) 322 323 // And finally report success. 324 body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}` 325 gitjujutesting.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest))) 326 327 ctx, err := testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"cs:~user/precise/wordpress"}, s.dir) 328 c.Assert(err, jc.ErrorIsNil) 329 c.Assert(testing.Stdout(ctx), gc.Equals, "cs:~user/precise/wordpress-42\n") 330 331 // Ensure the branch was actually pushed. 332 pushDigest, err := pushBranch.RevisionId() 333 c.Assert(err, jc.ErrorIsNil) 334 c.Assert(pushDigest, gc.Equals, digest) 335 336 // And that all the requests were sent with the proper data. 337 req := gitjujutesting.Server.WaitRequest() 338 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 339 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress@"+digest) 340 341 for i := 0; i < 3; i++ { 342 // The second request grabs tip to see the current state, and the 343 // following requests are done after pushing to see when it changes. 344 req = gitjujutesting.Server.WaitRequest() 345 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 346 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress") 347 } 348 } 349 350 func (s *PublishSuite) TestFullPublishRace(c *gc.C) { 351 addMeta(c, s.branch, "") 352 353 digest, err := s.branch.RevisionId() 354 c.Assert(err, jc.ErrorIsNil) 355 356 pushBranch := bzr.New(c.MkDir()) 357 err = pushBranch.Init() 358 c.Assert(err, jc.ErrorIsNil) 359 360 cmd := &PublishCommand{} 361 cmd.ChangePushLocation(func(location string) string { 362 c.Assert(location, gc.Equals, "lp:~user/charms/precise/wordpress/trunk") 363 return pushBranch.Location() 364 }) 365 cmd.SetPollDelay(pollDelay) 366 367 var body string 368 369 // The local digest isn't found. 370 body = `{"cs:~user/precise/wordpress": {"kind": "", "errors": ["entry not found"]}}` 371 gitjujutesting.Server.Response(200, nil, []byte(body)) 372 373 // And tip isn't found either, meaning the charm was never published. 374 gitjujutesting.Server.Response(200, nil, []byte(body)) 375 376 // After the branch is pushed we fake the publishing delay. 377 gitjujutesting.Server.Response(200, nil, []byte(body)) 378 379 // But, surprisingly, the digest changed to something else entirely. 380 body = `{"cs:~user/precise/wordpress": {"kind": "published", "digest": "surprising-digest", "revision": 42}}` 381 gitjujutesting.Server.Response(200, nil, []byte(body)) 382 383 _, err = testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"cs:~user/precise/wordpress"}, s.dir) 384 c.Assert(err, gc.ErrorMatches, `charm changed but not to local charm digest; publishing race\?`) 385 386 // Ensure the branch was actually pushed. 387 pushDigest, err := pushBranch.RevisionId() 388 c.Assert(err, jc.ErrorIsNil) 389 c.Assert(pushDigest, gc.Equals, digest) 390 391 // And that all the requests were sent with the proper data. 392 req := gitjujutesting.Server.WaitRequest() 393 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 394 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress@"+digest) 395 396 for i := 0; i < 3; i++ { 397 // The second request grabs tip to see the current state, and the 398 // following requests are done after pushing to see when it changes. 399 req = gitjujutesting.Server.WaitRequest() 400 c.Assert(req.URL.Path, gc.Equals, "/charm-event") 401 c.Assert(req.Form.Get("charms"), gc.Equals, "cs:~user/precise/wordpress") 402 } 403 }