github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 gitjujutesting "github.com/juju/testing" 11 gc "launchpad.net/gocheck" 12 13 "github.com/juju/juju/bzr" 14 "github.com/juju/juju/charm" 15 "github.com/juju/juju/cmd" 16 "github.com/juju/juju/cmd/envcmd" 17 "github.com/juju/juju/testing" 18 ) 19 20 // Sadly, this is a very slow test suite, heavily dominated by calls to bzr. 21 22 type PublishSuite struct { 23 testing.FakeJujuHomeSuite 24 testing.HTTPSuite 25 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, envcmd.Wrap(&PublishCommand{}), args, s.dir) 56 } 57 58 const pollDelay = testing.ShortWait 59 60 func (s *PublishSuite) SetUpSuite(c *gc.C) { 61 s.FakeJujuHomeSuite.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.FakeJujuHomeSuite.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.FakeJujuHomeSuite.SetUpTest(c) 77 s.HTTPSuite.SetUpTest(c) 78 s.FakeJujuHomeSuite.Home.AddFiles(c, gitjujutesting.TestFile{ 79 Name: ".bazaar/bazaar.conf", 80 Data: "[DEFAULT]\nemail = Test <testing@testing.invalid>\n", 81 }) 82 83 s.dir = c.MkDir() 84 s.branch = bzr.New(s.dir) 85 err := s.branch.Init() 86 c.Assert(err, gc.IsNil) 87 } 88 89 func (s *PublishSuite) TearDownTest(c *gc.C) { 90 s.HTTPSuite.TearDownTest(c) 91 s.FakeJujuHomeSuite.TearDownTest(c) 92 } 93 94 func (s *PublishSuite) TestNoBranch(c *gc.C) { 95 dir := c.MkDir() 96 _, err := testing.RunCommandInDir(c, envcmd.Wrap(&PublishCommand{}), []string{"cs:precise/wordpress"}, dir) 97 c.Assert(err, gc.ErrorMatches, fmt.Sprintf("not a charm branch: %s", dir)) 98 } 99 100 func (s *PublishSuite) TestEmpty(c *gc.C) { 101 _, err := s.runPublish(c, "cs:precise/wordpress") 102 c.Assert(err, gc.ErrorMatches, `cannot obtain local digest: branch has no content`) 103 } 104 105 func (s *PublishSuite) TestFrom(c *gc.C) { 106 _, err := testing.RunCommandInDir(c, envcmd.Wrap(&PublishCommand{}), []string{"--from", s.dir, "cs:precise/wordpress"}, c.MkDir()) 107 c.Assert(err, gc.ErrorMatches, `cannot obtain local digest: branch has no content`) 108 } 109 110 func (s *PublishSuite) TestMissingSeries(c *gc.C) { 111 _, err := s.runPublish(c, "cs:wordpress") 112 c.Assert(err, gc.ErrorMatches, `cannot infer charm URL for "cs:wordpress": no series provided`) 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, gc.IsNil) 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) TestInferURL(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, envcmd.Wrap(cmd), []string{"precise/wordpress"}, s.dir) 152 c.Assert(err, gc.IsNil) 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, gc.IsNil) 174 body := `{"cs:precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}` 175 testing.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest))) 176 177 ctx, err := s.runPublish(c, "cs:precise/wordpress") 178 c.Assert(err, gc.IsNil) 179 c.Assert(testing.Stdout(ctx), gc.Equals, "cs:precise/wordpress-42\n") 180 181 req := testing.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, gc.IsNil) 195 var body string 196 body = `{"cs:precise/wordpress": {"errors": ["entry not found"]}}` 197 testing.Server.Response(200, nil, []byte(body)) 198 body = `{"cs:precise/wordpress": {"kind": "published", "digest": %q, "revision": 42}}` 199 testing.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest))) 200 201 ctx, err := s.runPublish(c, "cs:precise/wordpress") 202 c.Assert(err, gc.IsNil) 203 c.Assert(testing.Stdout(ctx), gc.Equals, "cs:precise/wordpress-42\n") 204 205 req := testing.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 = testing.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, gc.IsNil) 220 body := `{"cs:precise/wordpress": {"kind": "publish-error", "digest": %q, "errors": ["an error"]}}` 221 testing.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 := testing.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, gc.IsNil) 236 237 pushBranch := bzr.New(c.MkDir()) 238 err = pushBranch.Init() 239 c.Assert(err, gc.IsNil) 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 testing.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 testing.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 testing.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 testing.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest))) 265 266 ctx, err := testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"cs:~user/precise/wordpress"}, s.dir) 267 c.Assert(err, gc.IsNil) 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, gc.IsNil) 273 c.Assert(pushDigest, gc.Equals, digest) 274 275 // And that all the requests were sent with the proper data. 276 req := testing.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 = testing.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, gc.IsNil) 294 295 pushBranch := bzr.New(c.MkDir()) 296 err = pushBranch.Init() 297 c.Assert(err, gc.IsNil) 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 testing.Server.Response(200, nil, []byte(body)) 311 312 // And tip isn't found either, meaning the charm was never published. 313 testing.Server.Response(200, nil, []byte(body)) 314 315 // After the branch is pushed we fake the publishing delay. 316 testing.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 testing.Server.Response(200, nil, []byte(fmt.Sprintf(body, digest))) 321 322 ctx, err := testing.RunCommandInDir(c, envcmd.Wrap(cmd), []string{"cs:~user/precise/wordpress"}, s.dir) 323 c.Assert(err, gc.IsNil) 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, gc.IsNil) 329 c.Assert(pushDigest, gc.Equals, digest) 330 331 // And that all the requests were sent with the proper data. 332 req := testing.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 = testing.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, gc.IsNil) 350 351 pushBranch := bzr.New(c.MkDir()) 352 err = pushBranch.Init() 353 c.Assert(err, gc.IsNil) 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 testing.Server.Response(200, nil, []byte(body)) 367 368 // And tip isn't found either, meaning the charm was never published. 369 testing.Server.Response(200, nil, []byte(body)) 370 371 // After the branch is pushed we fake the publishing delay. 372 testing.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 testing.Server.Response(200, nil, []byte(body)) 377 378 _, err = testing.RunCommandInDir(c, envcmd.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, gc.IsNil) 384 c.Assert(pushDigest, gc.Equals, digest) 385 386 // And that all the requests were sent with the proper data. 387 req := testing.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 = testing.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 }