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 }