launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/charm/repo_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charm_test 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 12 "github.com/loggo/loggo" 13 gc "launchpad.net/gocheck" 14 15 "launchpad.net/juju-core/charm" 16 charmtesting "launchpad.net/juju-core/charm/testing" 17 env_config "launchpad.net/juju-core/environs/config" 18 "launchpad.net/juju-core/testing" 19 jc "launchpad.net/juju-core/testing/checkers" 20 "launchpad.net/juju-core/testing/testbase" 21 ) 22 23 type StoreSuite struct { 24 testbase.LoggingSuite 25 server *charmtesting.MockStore 26 store *charm.CharmStore 27 } 28 29 var _ = gc.Suite(&StoreSuite{}) 30 31 func (s *StoreSuite) SetUpSuite(c *gc.C) { 32 s.LoggingSuite.SetUpSuite(c) 33 s.server = charmtesting.NewMockStore(c, map[string]int{ 34 "cs:series/good": 23, 35 "cs:series/unwise": 23, 36 "cs:series/better": 24, 37 "cs:series/best": 25, 38 }) 39 } 40 41 func (s *StoreSuite) SetUpTest(c *gc.C) { 42 s.LoggingSuite.SetUpTest(c) 43 s.PatchValue(&charm.CacheDir, c.MkDir()) 44 s.store = charm.NewStore(s.server.Address()) 45 s.server.Downloads = nil 46 s.server.Authorizations = nil 47 s.server.Metadata = nil 48 } 49 50 type cleanupAdder interface { 51 AddCleanup(testbase.CleanupFunc) 52 } 53 54 func StartTestLog(suite cleanupAdder, level loggo.Level) *loggo.TestWriter { 55 tw := &loggo.TestWriter{} 56 if err := loggo.RegisterWriter("charm-tester", tw, level); err != nil { 57 panic(err) 58 } 59 suite.AddCleanup(func(c *gc.C) { 60 loggo.RemoveWriter("charm-tester") 61 }) 62 return tw 63 } 64 65 // Uses the TearDownTest from testbase.LoggingSuite 66 67 func (s *StoreSuite) TearDownSuite(c *gc.C) { 68 s.server.Close() 69 s.LoggingSuite.TearDownSuite(c) 70 } 71 72 func (s *StoreSuite) TestMissing(c *gc.C) { 73 charmURL := charm.MustParseURL("cs:series/missing") 74 expect := `charm not found: cs:series/missing` 75 _, err := charm.Latest(s.store, charmURL) 76 c.Assert(err, gc.ErrorMatches, expect) 77 _, err = s.store.Get(charmURL) 78 c.Assert(err, gc.ErrorMatches, expect) 79 } 80 81 func (s *StoreSuite) TestError(c *gc.C) { 82 charmURL := charm.MustParseURL("cs:series/borken") 83 expect := `charm info errors for "cs:series/borken": badness` 84 _, err := charm.Latest(s.store, charmURL) 85 c.Assert(err, gc.ErrorMatches, expect) 86 _, err = s.store.Get(charmURL) 87 c.Assert(err, gc.ErrorMatches, expect) 88 } 89 90 func (s *StoreSuite) TestWarning(c *gc.C) { 91 charmURL := charm.MustParseURL("cs:series/unwise") 92 tw := StartTestLog(s, loggo.WARNING) 93 r, err := charm.Latest(s.store, charmURL) 94 c.Assert(r, gc.Equals, 23) 95 c.Assert(err, gc.IsNil) 96 expect := jc.SimpleMessage{ 97 loggo.WARNING, 98 `charm store reports for "cs:series/unwise": foolishness`, 99 } 100 c.Assert(tw.Log, jc.LogMatches, []jc.SimpleMessage{expect}) 101 ch, err := s.store.Get(charmURL) 102 c.Assert(ch, gc.NotNil) 103 c.Assert(err, gc.IsNil) 104 c.Assert(tw.Log, jc.LogMatches, []jc.SimpleMessage{expect, expect}) 105 } 106 107 func (s *StoreSuite) TestLatest(c *gc.C) { 108 urls := []*charm.URL{ 109 charm.MustParseURL("cs:series/good"), 110 charm.MustParseURL("cs:series/good-2"), 111 charm.MustParseURL("cs:series/good-99"), 112 } 113 revInfo, err := s.store.Latest(urls...) 114 c.Assert(err, gc.IsNil) 115 c.Assert(revInfo, gc.DeepEquals, []charm.CharmRevision{ 116 {23, "2c9f01a53a73c221d5360207e7bb2f887ff83c32b04e58aca76c4d99fd071ec7", nil}, 117 {23, "2c9f01a53a73c221d5360207e7bb2f887ff83c32b04e58aca76c4d99fd071ec7", nil}, 118 {23, "2c9f01a53a73c221d5360207e7bb2f887ff83c32b04e58aca76c4d99fd071ec7", nil}, 119 }) 120 } 121 122 func (s *StoreSuite) assertCached(c *gc.C, charmURL *charm.URL) { 123 s.server.Downloads = nil 124 ch, err := s.store.Get(charmURL) 125 c.Assert(err, gc.IsNil) 126 c.Assert(ch, gc.NotNil) 127 c.Assert(s.server.Downloads, gc.IsNil) 128 } 129 130 func (s *StoreSuite) TestGetCacheImplicitRevision(c *gc.C) { 131 base := "cs:series/good" 132 charmURL := charm.MustParseURL(base) 133 revCharmURL := charm.MustParseURL(base + "-23") 134 ch, err := s.store.Get(charmURL) 135 c.Assert(err, gc.IsNil) 136 c.Assert(ch, gc.NotNil) 137 c.Assert(s.server.Downloads, gc.DeepEquals, []*charm.URL{revCharmURL}) 138 s.assertCached(c, charmURL) 139 s.assertCached(c, revCharmURL) 140 } 141 142 func (s *StoreSuite) TestGetCacheExplicitRevision(c *gc.C) { 143 base := "cs:series/good-12" 144 charmURL := charm.MustParseURL(base) 145 ch, err := s.store.Get(charmURL) 146 c.Assert(err, gc.IsNil) 147 c.Assert(ch, gc.NotNil) 148 c.Assert(s.server.Downloads, gc.DeepEquals, []*charm.URL{charmURL}) 149 s.assertCached(c, charmURL) 150 } 151 152 func (s *StoreSuite) TestGetBadCache(c *gc.C) { 153 c.Assert(os.Mkdir(filepath.Join(charm.CacheDir, "cache"), 0777), gc.IsNil) 154 base := "cs:series/good" 155 charmURL := charm.MustParseURL(base) 156 revCharmURL := charm.MustParseURL(base + "-23") 157 name := charm.Quote(revCharmURL.String()) + ".charm" 158 err := ioutil.WriteFile(filepath.Join(charm.CacheDir, "cache", name), nil, 0666) 159 c.Assert(err, gc.IsNil) 160 ch, err := s.store.Get(charmURL) 161 c.Assert(err, gc.IsNil) 162 c.Assert(ch, gc.NotNil) 163 c.Assert(s.server.Downloads, gc.DeepEquals, []*charm.URL{revCharmURL}) 164 s.assertCached(c, charmURL) 165 s.assertCached(c, revCharmURL) 166 } 167 168 // The following tests cover the low-level CharmStore-specific API. 169 170 func (s *StoreSuite) TestInfo(c *gc.C) { 171 charmURLs := []*charm.URL{ 172 charm.MustParseURL("cs:series/good"), 173 charm.MustParseURL("cs:series/better"), 174 charm.MustParseURL("cs:series/best"), 175 } 176 infos, err := s.store.Info(charmURLs...) 177 c.Assert(err, gc.IsNil) 178 c.Assert(infos, gc.HasLen, 3) 179 expected := []int{23, 24, 25} 180 for i, info := range infos { 181 c.Assert(info.Errors, gc.IsNil) 182 c.Assert(info.Revision, gc.Equals, expected[i]) 183 } 184 } 185 186 func (s *StoreSuite) TestInfoNotFound(c *gc.C) { 187 charmURL := charm.MustParseURL("cs:series/missing") 188 info, err := s.store.Info(charmURL) 189 c.Assert(err, gc.IsNil) 190 c.Assert(info, gc.HasLen, 1) 191 c.Assert(info[0].Errors, gc.HasLen, 1) 192 c.Assert(info[0].Errors[0], gc.Matches, `charm not found: cs:series/missing`) 193 } 194 195 func (s *StoreSuite) TestInfoError(c *gc.C) { 196 charmURL := charm.MustParseURL("cs:series/borken") 197 info, err := s.store.Info(charmURL) 198 c.Assert(err, gc.IsNil) 199 c.Assert(info, gc.HasLen, 1) 200 c.Assert(info[0].Errors, gc.DeepEquals, []string{"badness"}) 201 } 202 203 func (s *StoreSuite) TestInfoWarning(c *gc.C) { 204 charmURL := charm.MustParseURL("cs:series/unwise") 205 info, err := s.store.Info(charmURL) 206 c.Assert(err, gc.IsNil) 207 c.Assert(info, gc.HasLen, 1) 208 c.Assert(info[0].Warnings, gc.DeepEquals, []string{"foolishness"}) 209 } 210 211 func (s *StoreSuite) TestInfoDNSError(c *gc.C) { 212 store := charm.NewStore("http://0.1.2.3") 213 charmURL := charm.MustParseURL("cs:series/good") 214 resp, err := store.Info(charmURL) 215 c.Assert(resp, gc.IsNil) 216 expect := `Cannot access the charm store. Are you connected to the internet. Error details:.*` 217 c.Assert(err, gc.ErrorMatches, expect) 218 } 219 220 func (s *StoreSuite) TestEvent(c *gc.C) { 221 charmURL := charm.MustParseURL("cs:series/good") 222 event, err := s.store.Event(charmURL, "") 223 c.Assert(err, gc.IsNil) 224 c.Assert(event.Errors, gc.IsNil) 225 c.Assert(event.Revision, gc.Equals, 23) 226 c.Assert(event.Digest, gc.Equals, "the-digest") 227 } 228 229 func (s *StoreSuite) TestEventWithDigest(c *gc.C) { 230 charmURL := charm.MustParseURL("cs:series/good") 231 event, err := s.store.Event(charmURL, "the-digest") 232 c.Assert(err, gc.IsNil) 233 c.Assert(event.Errors, gc.IsNil) 234 c.Assert(event.Revision, gc.Equals, 23) 235 c.Assert(event.Digest, gc.Equals, "the-digest") 236 } 237 238 func (s *StoreSuite) TestEventNotFound(c *gc.C) { 239 charmURL := charm.MustParseURL("cs:series/missing") 240 event, err := s.store.Event(charmURL, "") 241 c.Assert(err, gc.ErrorMatches, `charm event not found for "cs:series/missing"`) 242 c.Assert(event, gc.IsNil) 243 } 244 245 func (s *StoreSuite) TestEventNotFoundDigest(c *gc.C) { 246 charmURL := charm.MustParseURL("cs:series/good") 247 event, err := s.store.Event(charmURL, "missing-digest") 248 c.Assert(err, gc.ErrorMatches, `charm event not found for "cs:series/good" with digest "missing-digest"`) 249 c.Assert(event, gc.IsNil) 250 } 251 252 func (s *StoreSuite) TestEventError(c *gc.C) { 253 charmURL := charm.MustParseURL("cs:series/borken") 254 event, err := s.store.Event(charmURL, "") 255 c.Assert(err, gc.IsNil) 256 c.Assert(event.Errors, gc.DeepEquals, []string{"badness"}) 257 } 258 259 func (s *StoreSuite) TestAuthorization(c *gc.C) { 260 config := testing.CustomEnvironConfig(c, 261 testing.Attrs{"charm-store-auth": "token=value"}) 262 store := env_config.AuthorizeCharmRepo(s.store, config) 263 264 base := "cs:series/good" 265 charmURL := charm.MustParseURL(base) 266 _, err := store.Get(charmURL) 267 268 c.Assert(err, gc.IsNil) 269 270 c.Assert(s.server.Authorizations, gc.HasLen, 1) 271 c.Assert(s.server.Authorizations[0], gc.Equals, "charmstore token=value") 272 } 273 274 func (s *StoreSuite) TestNilAuthorization(c *gc.C) { 275 config := testing.EnvironConfig(c) 276 store := env_config.AuthorizeCharmRepo(s.store, config) 277 278 base := "cs:series/good" 279 charmURL := charm.MustParseURL(base) 280 _, err := store.Get(charmURL) 281 282 c.Assert(err, gc.IsNil) 283 c.Assert(s.server.Authorizations, gc.HasLen, 0) 284 } 285 286 func (s *StoreSuite) TestMetadata(c *gc.C) { 287 store := s.store.WithJujuAttrs("juju-metadata") 288 289 base := "cs:series/good" 290 charmURL := charm.MustParseURL(base) 291 _, err := store.Get(charmURL) 292 293 c.Assert(err, gc.IsNil) 294 c.Assert(s.server.Metadata, gc.HasLen, 1) 295 c.Assert(s.server.Metadata[0], gc.Equals, "juju-metadata") 296 } 297 298 func (s *StoreSuite) TestNilMetadata(c *gc.C) { 299 base := "cs:series/good" 300 charmURL := charm.MustParseURL(base) 301 _, err := s.store.Get(charmURL) 302 303 c.Assert(err, gc.IsNil) 304 c.Assert(s.server.Metadata, gc.HasLen, 0) 305 } 306 307 func (s *StoreSuite) TestEventWarning(c *gc.C) { 308 charmURL := charm.MustParseURL("cs:series/unwise") 309 event, err := s.store.Event(charmURL, "") 310 c.Assert(err, gc.IsNil) 311 c.Assert(event.Warnings, gc.DeepEquals, []string{"foolishness"}) 312 } 313 314 func (s *StoreSuite) TestBranchLocation(c *gc.C) { 315 charmURL := charm.MustParseURL("cs:series/name") 316 location := s.store.BranchLocation(charmURL) 317 c.Assert(location, gc.Equals, "lp:charms/series/name") 318 319 charmURL = charm.MustParseURL("cs:~user/series/name") 320 location = s.store.BranchLocation(charmURL) 321 c.Assert(location, gc.Equals, "lp:~user/charms/series/name/trunk") 322 } 323 324 func (s *StoreSuite) TestCharmURL(c *gc.C) { 325 tests := []struct{ url, loc string }{ 326 {"cs:precise/wordpress", "lp:charms/precise/wordpress"}, 327 {"cs:precise/wordpress", "http://launchpad.net/+branch/charms/precise/wordpress"}, 328 {"cs:precise/wordpress", "https://launchpad.net/+branch/charms/precise/wordpress"}, 329 {"cs:precise/wordpress", "http://code.launchpad.net/+branch/charms/precise/wordpress"}, 330 {"cs:precise/wordpress", "https://code.launchpad.net/+branch/charms/precise/wordpress"}, 331 {"cs:precise/wordpress", "bzr+ssh://bazaar.launchpad.net/+branch/charms/precise/wordpress"}, 332 {"cs:~charmers/precise/wordpress", "lp:~charmers/charms/precise/wordpress/trunk"}, 333 {"cs:~charmers/precise/wordpress", "http://launchpad.net/~charmers/charms/precise/wordpress/trunk"}, 334 {"cs:~charmers/precise/wordpress", "https://launchpad.net/~charmers/charms/precise/wordpress/trunk"}, 335 {"cs:~charmers/precise/wordpress", "http://code.launchpad.net/~charmers/charms/precise/wordpress/trunk"}, 336 {"cs:~charmers/precise/wordpress", "https://code.launchpad.net/~charmers/charms/precise/wordpress/trunk"}, 337 {"cs:~charmers/precise/wordpress", "http://launchpad.net/+branch/~charmers/charms/precise/wordpress/trunk"}, 338 {"cs:~charmers/precise/wordpress", "https://launchpad.net/+branch/~charmers/charms/precise/wordpress/trunk"}, 339 {"cs:~charmers/precise/wordpress", "http://code.launchpad.net/+branch/~charmers/charms/precise/wordpress/trunk"}, 340 {"cs:~charmers/precise/wordpress", "https://code.launchpad.net/+branch/~charmers/charms/precise/wordpress/trunk"}, 341 {"cs:~charmers/precise/wordpress", "bzr+ssh://bazaar.launchpad.net/~charmers/charms/precise/wordpress/trunk"}, 342 {"cs:~charmers/precise/wordpress", "bzr+ssh://bazaar.launchpad.net/~charmers/charms/precise/wordpress/trunk/"}, 343 {"cs:~charmers/precise/wordpress", "~charmers/charms/precise/wordpress/trunk"}, 344 {"", "lp:~charmers/charms/precise/wordpress/whatever"}, 345 {"", "lp:~charmers/whatever/precise/wordpress/trunk"}, 346 {"", "lp:whatever/precise/wordpress"}, 347 } 348 for _, t := range tests { 349 charmURL, err := s.store.CharmURL(t.loc) 350 if t.url == "" { 351 c.Assert(err, gc.ErrorMatches, fmt.Sprintf("unknown branch location: %q", t.loc)) 352 } else { 353 c.Assert(err, gc.IsNil) 354 c.Assert(charmURL.String(), gc.Equals, t.url) 355 } 356 } 357 } 358 359 type LocalRepoSuite struct { 360 testbase.LoggingSuite 361 repo *charm.LocalRepository 362 seriesPath string 363 } 364 365 var _ = gc.Suite(&LocalRepoSuite{}) 366 367 func (s *LocalRepoSuite) SetUpTest(c *gc.C) { 368 s.LoggingSuite.SetUpTest(c) 369 root := c.MkDir() 370 s.repo = &charm.LocalRepository{root} 371 s.seriesPath = filepath.Join(root, "quantal") 372 c.Assert(os.Mkdir(s.seriesPath, 0777), gc.IsNil) 373 } 374 375 func (s *LocalRepoSuite) addBundle(name string) string { 376 return testing.Charms.BundlePath(s.seriesPath, name) 377 } 378 379 func (s *LocalRepoSuite) addDir(name string) string { 380 return testing.Charms.ClonedDirPath(s.seriesPath, name) 381 } 382 383 func (s *LocalRepoSuite) checkNotFoundErr(c *gc.C, err error, charmURL *charm.URL) { 384 expect := `charm not found in "` + s.repo.Path + `": ` + charmURL.String() 385 c.Check(err, gc.ErrorMatches, expect) 386 } 387 388 func (s *LocalRepoSuite) TestMissingCharm(c *gc.C) { 389 for i, str := range []string{ 390 "local:quantal/zebra", "local:badseries/zebra", 391 } { 392 c.Logf("test %d: %s", i, str) 393 charmURL := charm.MustParseURL(str) 394 _, err := charm.Latest(s.repo, charmURL) 395 s.checkNotFoundErr(c, err, charmURL) 396 _, err = s.repo.Get(charmURL) 397 s.checkNotFoundErr(c, err, charmURL) 398 } 399 } 400 401 func (s *LocalRepoSuite) TestMissingRepo(c *gc.C) { 402 c.Assert(os.RemoveAll(s.repo.Path), gc.IsNil) 403 _, err := charm.Latest(s.repo, charm.MustParseURL("local:quantal/zebra")) 404 c.Assert(err, gc.ErrorMatches, `no repository found at ".*"`) 405 _, err = s.repo.Get(charm.MustParseURL("local:quantal/zebra")) 406 c.Assert(err, gc.ErrorMatches, `no repository found at ".*"`) 407 c.Assert(ioutil.WriteFile(s.repo.Path, nil, 0666), gc.IsNil) 408 _, err = charm.Latest(s.repo, charm.MustParseURL("local:quantal/zebra")) 409 c.Assert(err, gc.ErrorMatches, `no repository found at ".*"`) 410 _, err = s.repo.Get(charm.MustParseURL("local:quantal/zebra")) 411 c.Assert(err, gc.ErrorMatches, `no repository found at ".*"`) 412 } 413 414 func (s *LocalRepoSuite) TestMultipleVersions(c *gc.C) { 415 charmURL := charm.MustParseURL("local:quantal/upgrade") 416 s.addDir("upgrade1") 417 rev, err := charm.Latest(s.repo, charmURL) 418 c.Assert(err, gc.IsNil) 419 c.Assert(rev, gc.Equals, 1) 420 ch, err := s.repo.Get(charmURL) 421 c.Assert(err, gc.IsNil) 422 c.Assert(ch.Revision(), gc.Equals, 1) 423 424 s.addDir("upgrade2") 425 rev, err = charm.Latest(s.repo, charmURL) 426 c.Assert(err, gc.IsNil) 427 c.Assert(rev, gc.Equals, 2) 428 ch, err = s.repo.Get(charmURL) 429 c.Assert(err, gc.IsNil) 430 c.Assert(ch.Revision(), gc.Equals, 2) 431 432 revCharmURL := charmURL.WithRevision(1) 433 rev, err = charm.Latest(s.repo, revCharmURL) 434 c.Assert(err, gc.IsNil) 435 c.Assert(rev, gc.Equals, 2) 436 ch, err = s.repo.Get(revCharmURL) 437 c.Assert(err, gc.IsNil) 438 c.Assert(ch.Revision(), gc.Equals, 1) 439 440 badRevCharmURL := charmURL.WithRevision(33) 441 rev, err = charm.Latest(s.repo, badRevCharmURL) 442 c.Assert(err, gc.IsNil) 443 c.Assert(rev, gc.Equals, 2) 444 _, err = s.repo.Get(badRevCharmURL) 445 s.checkNotFoundErr(c, err, badRevCharmURL) 446 } 447 448 func (s *LocalRepoSuite) TestBundle(c *gc.C) { 449 charmURL := charm.MustParseURL("local:quantal/dummy") 450 s.addBundle("dummy") 451 452 rev, err := charm.Latest(s.repo, charmURL) 453 c.Assert(err, gc.IsNil) 454 c.Assert(rev, gc.Equals, 1) 455 ch, err := s.repo.Get(charmURL) 456 c.Assert(err, gc.IsNil) 457 c.Assert(ch.Revision(), gc.Equals, 1) 458 } 459 460 func (s *LocalRepoSuite) TestLogsErrors(c *gc.C) { 461 err := ioutil.WriteFile(filepath.Join(s.seriesPath, "blah.charm"), nil, 0666) 462 c.Assert(err, gc.IsNil) 463 err = os.Mkdir(filepath.Join(s.seriesPath, "blah"), 0666) 464 c.Assert(err, gc.IsNil) 465 samplePath := s.addDir("upgrade2") 466 gibberish := []byte("don't parse me by") 467 err = ioutil.WriteFile(filepath.Join(samplePath, "metadata.yaml"), gibberish, 0666) 468 c.Assert(err, gc.IsNil) 469 470 tw := StartTestLog(s, loggo.WARNING) 471 charmURL := charm.MustParseURL("local:quantal/dummy") 472 s.addDir("dummy") 473 ch, err := s.repo.Get(charmURL) 474 c.Assert(err, gc.IsNil) 475 c.Assert(ch.Revision(), gc.Equals, 1) 476 c.Assert(tw.Log, jc.LogMatches, []jc.SimpleMessage{ 477 {loggo.WARNING, `failed to load charm at ".*/quantal/blah": .*`}, 478 {loggo.WARNING, `failed to load charm at ".*/quantal/blah.charm": .*`}, 479 {loggo.WARNING, `failed to load charm at ".*/quantal/upgrade2": .*`}, 480 }) 481 } 482 483 func renameSibling(c *gc.C, path, name string) { 484 c.Assert(os.Rename(path, filepath.Join(filepath.Dir(path), name)), gc.IsNil) 485 } 486 487 func (s *LocalRepoSuite) TestIgnoresUnpromisingNames(c *gc.C) { 488 err := ioutil.WriteFile(filepath.Join(s.seriesPath, "blah.notacharm"), nil, 0666) 489 c.Assert(err, gc.IsNil) 490 err = os.Mkdir(filepath.Join(s.seriesPath, ".blah"), 0666) 491 c.Assert(err, gc.IsNil) 492 renameSibling(c, s.addDir("dummy"), ".dummy") 493 renameSibling(c, s.addBundle("dummy"), "dummy.notacharm") 494 charmURL := charm.MustParseURL("local:quantal/dummy") 495 496 tw := StartTestLog(s, loggo.WARNING) 497 _, err = s.repo.Get(charmURL) 498 s.checkNotFoundErr(c, err, charmURL) 499 _, err = charm.Latest(s.repo, charmURL) 500 s.checkNotFoundErr(c, err, charmURL) 501 c.Assert(tw.Log, gc.HasLen, 0) 502 } 503 504 func (s *LocalRepoSuite) TestFindsSymlinks(c *gc.C) { 505 realPath := testing.Charms.ClonedDirPath(c.MkDir(), "dummy") 506 linkPath := filepath.Join(s.seriesPath, "dummy") 507 err := os.Symlink(realPath, linkPath) 508 c.Assert(err, gc.IsNil) 509 ch, err := s.repo.Get(charm.MustParseURL("local:quantal/dummy")) 510 c.Assert(err, gc.IsNil) 511 checkDummy(c, ch, linkPath) 512 }