github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/environs/sync/sync_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package sync_test 5 6 import ( 7 "bytes" 8 "compress/gzip" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "os" 14 "os/exec" 15 "path" 16 "path/filepath" 17 "runtime" 18 "sort" 19 "testing" 20 21 "github.com/juju/errors" 22 gitjujutesting "github.com/juju/testing" 23 jc "github.com/juju/testing/checkers" 24 "github.com/juju/utils" 25 "github.com/juju/utils/arch" 26 "github.com/juju/utils/series" 27 "github.com/juju/utils/tar" 28 "github.com/juju/version" 29 gc "gopkg.in/check.v1" 30 31 "github.com/juju/juju/environs" 32 "github.com/juju/juju/environs/filestorage" 33 "github.com/juju/juju/environs/simplestreams" 34 "github.com/juju/juju/environs/storage" 35 "github.com/juju/juju/environs/sync" 36 envtesting "github.com/juju/juju/environs/testing" 37 envtools "github.com/juju/juju/environs/tools" 38 toolstesting "github.com/juju/juju/environs/tools/testing" 39 "github.com/juju/juju/juju/names" 40 coretesting "github.com/juju/juju/testing" 41 coretools "github.com/juju/juju/tools" 42 jujuversion "github.com/juju/juju/version" 43 ) 44 45 func TestPackage(t *testing.T) { 46 gc.TestingT(t) 47 } 48 49 type syncSuite struct { 50 coretesting.FakeJujuXDGDataHomeSuite 51 envtesting.ToolsFixture 52 storage storage.Storage 53 localStorage string 54 } 55 56 var _ = gc.Suite(&syncSuite{}) 57 var _ = gc.Suite(&uploadSuite{}) 58 var _ = gc.Suite(&badBuildSuite{}) 59 60 func (s *syncSuite) setUpTest(c *gc.C) { 61 if runtime.GOOS == "windows" { 62 c.Skip("issue 1403084: Currently does not work because of jujud problems") 63 } 64 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 65 s.ToolsFixture.SetUpTest(c) 66 67 // It's important that this be v1.8.x to match the test data. 68 s.PatchValue(&jujuversion.Current, version.MustParse("1.8.3")) 69 70 // Create a source storage. 71 baseDir := c.MkDir() 72 stor, err := filestorage.NewFileStorageWriter(baseDir) 73 c.Assert(err, jc.ErrorIsNil) 74 s.storage = stor 75 76 // Create a local tools directory. 77 s.localStorage = c.MkDir() 78 79 // Populate both local and default tools locations with the public tools. 80 versionStrings := make([]string, len(vAll)) 81 for i, vers := range vAll { 82 versionStrings[i] = vers.String() 83 } 84 toolstesting.MakeTools(c, baseDir, "released", versionStrings) 85 toolstesting.MakeTools(c, s.localStorage, "released", versionStrings) 86 87 // Switch the default tools location. 88 baseURL, err := s.storage.URL(storage.BaseToolsPath) 89 c.Assert(err, jc.ErrorIsNil) 90 s.PatchValue(&envtools.DefaultBaseURL, baseURL) 91 } 92 93 func (s *syncSuite) tearDownTest(c *gc.C) { 94 s.ToolsFixture.TearDownTest(c) 95 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 96 } 97 98 var tests = []struct { 99 description string 100 ctx *sync.SyncContext 101 source bool 102 tools []version.Binary 103 version version.Number 104 major int 105 minor int 106 }{ 107 { 108 description: "copy newest from the filesystem", 109 ctx: &sync.SyncContext{}, 110 source: true, 111 tools: v180all, 112 }, 113 { 114 description: "copy newest from the dummy model", 115 ctx: &sync.SyncContext{}, 116 tools: v180all, 117 }, 118 { 119 description: "copy matching dev from the dummy model", 120 ctx: &sync.SyncContext{}, 121 version: version.MustParse("1.9.3"), 122 tools: v190all, 123 }, 124 { 125 description: "copy matching major, minor from the dummy model", 126 ctx: &sync.SyncContext{}, 127 major: 3, 128 minor: 2, 129 tools: []version.Binary{v320p64}, 130 }, 131 { 132 description: "copy matching major, minor dev from the dummy model", 133 ctx: &sync.SyncContext{}, 134 major: 3, 135 minor: 1, 136 tools: []version.Binary{v310p64}, 137 }, 138 { 139 description: "copy all from the dummy model", 140 ctx: &sync.SyncContext{ 141 AllVersions: true, 142 }, 143 tools: v1all, 144 }, 145 } 146 147 func (s *syncSuite) TestSyncing(c *gc.C) { 148 for i, test := range tests { 149 // Perform all tests in a "clean" environment. 150 func() { 151 s.setUpTest(c) 152 defer s.tearDownTest(c) 153 154 c.Logf("test %d: %s", i, test.description) 155 156 if test.source { 157 test.ctx.Source = s.localStorage 158 } 159 if test.version != version.Zero { 160 jujuversion.Current = test.version 161 } 162 if test.major > 0 { 163 test.ctx.MajorVersion = test.major 164 test.ctx.MinorVersion = test.minor 165 } 166 uploader := fakeToolsUploader{ 167 uploaded: make(map[version.Binary]bool), 168 } 169 test.ctx.TargetToolsFinder = mockToolsFinder{} 170 test.ctx.TargetToolsUploader = &uploader 171 172 err := sync.SyncTools(test.ctx) 173 c.Assert(err, jc.ErrorIsNil) 174 175 ds, err := sync.SelectSourceDatasource(test.ctx) 176 c.Assert(err, jc.ErrorIsNil) 177 178 // This data source does not require to contain signed data. 179 // However, it may still contain it. 180 // Since we will always try to read signed data first, 181 // we want to be able to try to read this signed data 182 // with public key with Juju-known public key for tools. 183 // Bugs #1542127, #1542131 184 c.Assert(ds.PublicSigningKey(), gc.Not(gc.Equals), "") 185 186 var uploaded []version.Binary 187 for v := range uploader.uploaded { 188 uploaded = append(uploaded, v) 189 } 190 c.Assert(uploaded, jc.SameContents, test.tools) 191 }() 192 } 193 } 194 195 type fakeToolsUploader struct { 196 uploaded map[version.Binary]bool 197 } 198 199 func (u *fakeToolsUploader) UploadTools(toolsDir, stream string, tools *coretools.Tools, data []byte) error { 200 u.uploaded[tools.Version] = true 201 return nil 202 } 203 204 var ( 205 v100p64 = version.MustParseBinary("1.0.0-precise-amd64") 206 v100q64 = version.MustParseBinary("1.0.0-quantal-amd64") 207 v100q32 = version.MustParseBinary("1.0.0-quantal-i386") 208 v100all = []version.Binary{v100p64, v100q64, v100q32} 209 v180q64 = version.MustParseBinary("1.8.0-quantal-amd64") 210 v180p32 = version.MustParseBinary("1.8.0-precise-i386") 211 v180all = []version.Binary{v180q64, v180p32} 212 v190q64 = version.MustParseBinary("1.9.0-quantal-amd64") 213 v190p32 = version.MustParseBinary("1.9.0-precise-i386") 214 v190all = []version.Binary{v190q64, v190p32} 215 v1all = append(append(v100all, v180all...), v190all...) 216 v200p64 = version.MustParseBinary("2.0.0-precise-amd64") 217 v310p64 = version.MustParseBinary("3.1.0-precise-amd64") 218 v320p64 = version.MustParseBinary("3.2.0-precise-amd64") 219 vAll = append(append(v1all, v200p64), v310p64, v320p64) 220 ) 221 222 type uploadSuite struct { 223 env environs.Environ 224 coretesting.FakeJujuXDGDataHomeSuite 225 envtesting.ToolsFixture 226 targetStorage storage.Storage 227 } 228 229 func (s *uploadSuite) SetUpTest(c *gc.C) { 230 if runtime.GOOS == "windows" { 231 c.Skip("issue 1403084: Currently does not work because of jujud problems") 232 } 233 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 234 s.ToolsFixture.SetUpTest(c) 235 236 // Create a target storage. 237 stor, err := filestorage.NewFileStorageWriter(c.MkDir()) 238 c.Assert(err, jc.ErrorIsNil) 239 s.targetStorage = stor 240 } 241 242 func (s *uploadSuite) patchBundleTools(c *gc.C, v *version.Number) { 243 // Mock out building of tools. Sync should not care about the contents 244 // of tools archives, other than that they hash correctly. 245 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, v)) 246 } 247 248 func (s *uploadSuite) assertEqualsCurrentVersion(c *gc.C, v version.Binary) { 249 c.Assert(v, gc.Equals, version.Binary{Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries()}) 250 } 251 252 func (s *uploadSuite) TearDownTest(c *gc.C) { 253 s.ToolsFixture.TearDownTest(c) 254 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 255 } 256 257 func (s *uploadSuite) TestUpload(c *gc.C) { 258 s.patchBundleTools(c, nil) 259 t, err := sync.Upload(s.targetStorage, "released", nil) 260 c.Assert(err, jc.ErrorIsNil) 261 s.assertEqualsCurrentVersion(c, t.Version) 262 c.Assert(t.URL, gc.Not(gc.Equals), "") 263 s.assertUploadedTools(c, t, []string{series.HostSeries()}, "released") 264 } 265 266 func (s *uploadSuite) TestUploadFakeSeries(c *gc.C) { 267 s.patchBundleTools(c, nil) 268 seriesToUpload := "precise" 269 if seriesToUpload == series.HostSeries() { 270 seriesToUpload = "raring" 271 } 272 t, err := sync.Upload(s.targetStorage, "released", nil, "quantal", seriesToUpload) 273 c.Assert(err, jc.ErrorIsNil) 274 s.assertUploadedTools(c, t, []string{seriesToUpload, "quantal", series.HostSeries()}, "released") 275 } 276 277 func (s *uploadSuite) TestUploadAndForceVersion(c *gc.C) { 278 vers := jujuversion.Current 279 vers.Patch++ 280 s.patchBundleTools(c, &vers) 281 t, err := sync.Upload(s.targetStorage, "released", &vers) 282 c.Assert(err, jc.ErrorIsNil) 283 c.Assert(t.Version, gc.Equals, version.Binary{Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries()}) 284 } 285 286 func (s *uploadSuite) TestSyncTools(c *gc.C) { 287 s.patchBundleTools(c, nil) 288 builtTools, err := sync.BuildAgentTarball(true, nil, "released") 289 c.Assert(err, jc.ErrorIsNil) 290 t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools) 291 c.Assert(err, jc.ErrorIsNil) 292 s.assertEqualsCurrentVersion(c, t.Version) 293 c.Assert(t.URL, gc.Not(gc.Equals), "") 294 } 295 296 func (s *uploadSuite) TestSyncToolsFakeSeries(c *gc.C) { 297 s.patchBundleTools(c, nil) 298 seriesToUpload := "precise" 299 if seriesToUpload == series.HostSeries() { 300 seriesToUpload = "raring" 301 } 302 builtTools, err := sync.BuildAgentTarball(true, nil, "testing") 303 c.Assert(err, jc.ErrorIsNil) 304 305 t, err := sync.SyncBuiltTools(s.targetStorage, "testing", builtTools, "quantal", seriesToUpload) 306 c.Assert(err, jc.ErrorIsNil) 307 s.assertUploadedTools(c, t, []string{seriesToUpload, "quantal", series.HostSeries()}, "testing") 308 } 309 310 func (s *uploadSuite) TestSyncAndForceVersion(c *gc.C) { 311 vers := jujuversion.Current 312 vers.Patch++ 313 s.patchBundleTools(c, &vers) 314 builtTools, err := sync.BuildAgentTarball(true, &vers, "released") 315 c.Assert(err, jc.ErrorIsNil) 316 t, err := sync.SyncBuiltTools(s.targetStorage, "released", builtTools) 317 c.Assert(err, jc.ErrorIsNil) 318 // Reported version from build call matches the real jujud version. 319 c.Assert(t.Version, gc.Equals, version.Binary{Number: jujuversion.Current, Arch: arch.HostArch(), Series: series.HostSeries()}) 320 } 321 322 func (s *uploadSuite) assertUploadedTools(c *gc.C, t *coretools.Tools, expectSeries []string, stream string) { 323 s.assertEqualsCurrentVersion(c, t.Version) 324 expectRaw := downloadToolsRaw(c, t) 325 326 list, err := envtools.ReadList(s.targetStorage, stream, jujuversion.Current.Major, jujuversion.Current.Minor) 327 c.Assert(err, jc.ErrorIsNil) 328 c.Assert(list.AllSeries(), jc.SameContents, expectSeries) 329 sort.Strings(expectSeries) 330 c.Assert(list.AllSeries(), gc.DeepEquals, expectSeries) 331 for _, t := range list { 332 c.Logf("checking %s", t.URL) 333 c.Assert(t.Version.Number, gc.Equals, jujuversion.Current) 334 actualRaw := downloadToolsRaw(c, t) 335 c.Assert(string(actualRaw), gc.Equals, string(expectRaw)) 336 } 337 metadata, err := envtools.ReadMetadata(s.targetStorage, stream) 338 c.Assert(err, jc.ErrorIsNil) 339 c.Assert(metadata, gc.HasLen, 0) 340 } 341 342 // downloadToolsRaw downloads the supplied tools and returns the raw bytes. 343 func downloadToolsRaw(c *gc.C, t *coretools.Tools) []byte { 344 resp, err := utils.GetValidatingHTTPClient().Get(t.URL) 345 c.Assert(err, jc.ErrorIsNil) 346 defer resp.Body.Close() 347 c.Assert(resp.StatusCode, gc.Equals, http.StatusOK) 348 var buf bytes.Buffer 349 _, err = io.Copy(&buf, resp.Body) 350 c.Assert(err, jc.ErrorIsNil) 351 return buf.Bytes() 352 } 353 354 func bundleTools(c *gc.C) (version.Binary, string, error) { 355 f, err := ioutil.TempFile("", "juju-tgz") 356 c.Assert(err, jc.ErrorIsNil) 357 defer f.Close() 358 defer os.Remove(f.Name()) 359 360 return envtools.BundleTools(true, f, &jujuversion.Current) 361 } 362 363 type badBuildSuite struct { 364 env environs.Environ 365 gitjujutesting.LoggingSuite 366 gitjujutesting.CleanupSuite 367 envtesting.ToolsFixture 368 } 369 370 var badGo = ` 371 #!/bin/bash --norc 372 exit 1 373 `[1:] 374 375 func (s *badBuildSuite) SetUpSuite(c *gc.C) { 376 if runtime.GOOS == "windows" { 377 c.Skip("issue 1403084: Currently does not work because of jujud problems") 378 } 379 s.CleanupSuite.SetUpSuite(c) 380 s.LoggingSuite.SetUpSuite(c) 381 } 382 383 func (s *badBuildSuite) TearDownSuite(c *gc.C) { 384 s.LoggingSuite.TearDownSuite(c) 385 s.CleanupSuite.TearDownSuite(c) 386 } 387 388 func (s *badBuildSuite) SetUpTest(c *gc.C) { 389 s.CleanupSuite.SetUpTest(c) 390 s.LoggingSuite.SetUpTest(c) 391 s.ToolsFixture.SetUpTest(c) 392 393 // Mock go cmd 394 testPath := c.MkDir() 395 s.PatchEnvPathPrepend(testPath) 396 path := filepath.Join(testPath, "go") 397 err := ioutil.WriteFile(path, []byte(badGo), 0755) 398 c.Assert(err, jc.ErrorIsNil) 399 400 // Check mocked go cmd errors 401 out, err := exec.Command("go").CombinedOutput() 402 c.Assert(err, gc.ErrorMatches, "exit status 1") 403 c.Assert(string(out), gc.Equals, "") 404 } 405 406 func (s *badBuildSuite) TearDownTest(c *gc.C) { 407 s.ToolsFixture.TearDownTest(c) 408 s.LoggingSuite.TearDownTest(c) 409 s.CleanupSuite.TearDownTest(c) 410 } 411 412 func (s *badBuildSuite) assertEqualsCurrentVersion(c *gc.C, v version.Binary) { 413 current := version.Binary{ 414 Number: jujuversion.Current, 415 Arch: arch.HostArch(), 416 Series: series.HostSeries(), 417 } 418 c.Assert(v, gc.Equals, current) 419 } 420 421 func (s *badBuildSuite) TestBundleToolsBadBuild(c *gc.C) { 422 // Test that original bundleTools Func fails as expected 423 vers, sha256Hash, err := bundleTools(c) 424 c.Assert(vers, gc.DeepEquals, version.Binary{}) 425 c.Assert(sha256Hash, gc.Equals, "") 426 c.Assert(err, gc.ErrorMatches, `cannot build jujud agent binary from source: build command "go" failed: exit status 1; `) 427 428 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, &jujuversion.Current)) 429 430 // Test that BundleTools func passes after it is 431 // mocked out 432 vers, sha256Hash, err = bundleTools(c) 433 c.Assert(err, jc.ErrorIsNil) 434 c.Assert(vers.Number, gc.Equals, jujuversion.Current) 435 c.Assert(sha256Hash, gc.Equals, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") 436 } 437 438 func (s *badBuildSuite) TestUploadToolsBadBuild(c *gc.C) { 439 stor, err := filestorage.NewFileStorageWriter(c.MkDir()) 440 c.Assert(err, jc.ErrorIsNil) 441 442 // Test that original Upload Func fails as expected 443 t, err := sync.Upload(stor, "released", nil) 444 c.Assert(t, gc.IsNil) 445 c.Assert(err, gc.ErrorMatches, `cannot build jujud agent binary from source: build command \"go\" failed: exit status 1; `) 446 447 // Test that Upload func passes after BundleTools func is mocked out 448 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, nil)) 449 t, err = sync.Upload(stor, "released", nil) 450 c.Assert(err, jc.ErrorIsNil) 451 s.assertEqualsCurrentVersion(c, t.Version) 452 c.Assert(t.URL, gc.Not(gc.Equals), "") 453 } 454 455 func (s *badBuildSuite) TestBuildToolsBadBuild(c *gc.C) { 456 // Test that original BuildAgentTarball fails 457 builtTools, err := sync.BuildAgentTarball(true, nil, "released") 458 c.Assert(err, gc.ErrorMatches, `cannot build jujud agent binary from source: build command \"go\" failed: exit status 1; `) 459 c.Assert(builtTools, gc.IsNil) 460 461 // Test that BuildAgentTarball func passes after BundleTools func is 462 // mocked out 463 s.PatchValue(&envtools.BundleTools, toolstesting.GetMockBundleTools(c, nil)) 464 builtTools, err = sync.BuildAgentTarball(true, nil, "released") 465 s.assertEqualsCurrentVersion(c, builtTools.Version) 466 c.Assert(err, jc.ErrorIsNil) 467 } 468 469 func (s *badBuildSuite) TestBuildToolsNoBinaryAvailable(c *gc.C) { 470 builtTools, err := sync.BuildAgentTarball(false, nil, "released") 471 c.Assert(err, gc.ErrorMatches, `no prepackaged agent available and no jujud binary can be found`) 472 c.Assert(builtTools, gc.IsNil) 473 } 474 475 func (s *uploadSuite) TestMockBundleTools(c *gc.C) { 476 var ( 477 writer io.Writer 478 forceVersion *version.Number 479 n int 480 p bytes.Buffer 481 ) 482 p.WriteString("Hello World") 483 484 s.PatchValue(&envtools.BundleTools, func(build bool, writerArg io.Writer, forceVersionArg *version.Number) (vers version.Binary, sha256Hash string, err error) { 485 c.Assert(build, jc.IsTrue) 486 writer = writerArg 487 n, err = writer.Write(p.Bytes()) 488 c.Assert(err, jc.ErrorIsNil) 489 forceVersion = forceVersionArg 490 vers.Number = jujuversion.Current 491 return 492 }) 493 494 _, err := sync.BuildAgentTarball(true, &jujuversion.Current, "released") 495 c.Assert(err, jc.ErrorIsNil) 496 c.Assert(*forceVersion, gc.Equals, jujuversion.Current) 497 c.Assert(writer, gc.NotNil) 498 c.Assert(n, gc.Equals, len(p.Bytes())) 499 } 500 501 func (s *uploadSuite) TestMockBuildTools(c *gc.C) { 502 checkTools := func(tools *sync.BuiltAgent, vers version.Binary) { 503 c.Check(tools.StorageName, gc.Equals, "name") 504 c.Check(tools.Version, jc.DeepEquals, vers) 505 506 f, err := os.Open(filepath.Join(tools.Dir, "name")) 507 c.Assert(err, jc.ErrorIsNil) 508 defer f.Close() 509 510 gzr, err := gzip.NewReader(f) 511 c.Assert(err, jc.ErrorIsNil) 512 513 _, tr, err := tar.FindFile(gzr, names.Jujud) 514 c.Assert(err, jc.ErrorIsNil) 515 516 content, err := ioutil.ReadAll(tr) 517 c.Assert(err, jc.ErrorIsNil) 518 c.Check(string(content), gc.Equals, fmt.Sprintf("jujud contents %s", vers)) 519 } 520 521 current := version.MustParseBinary("1.9.1-trusty-amd64") 522 s.PatchValue(&jujuversion.Current, current.Number) 523 s.PatchValue(&arch.HostArch, func() string { return current.Arch }) 524 s.PatchValue(&series.HostSeries, func() string { return current.Series }) 525 buildToolsFunc := toolstesting.GetMockBuildTools(c) 526 builtTools, err := buildToolsFunc(true, nil, "released") 527 c.Assert(err, jc.ErrorIsNil) 528 checkTools(builtTools, current) 529 530 vers := version.MustParseBinary("1.5.3-trusty-amd64") 531 builtTools, err = buildToolsFunc(true, &vers.Number, "released") 532 c.Assert(err, jc.ErrorIsNil) 533 checkTools(builtTools, vers) 534 } 535 536 func (s *uploadSuite) TestStorageToolsUploaderWriteMirrors(c *gc.C) { 537 s.testStorageToolsUploaderWriteMirrors(c, envtools.WriteMirrors) 538 } 539 540 func (s *uploadSuite) TestStorageToolsUploaderDontWriteMirrors(c *gc.C) { 541 s.testStorageToolsUploaderWriteMirrors(c, envtools.DoNotWriteMirrors) 542 } 543 544 func (s *uploadSuite) testStorageToolsUploaderWriteMirrors(c *gc.C, writeMirrors envtools.ShouldWriteMirrors) { 545 storageDir := c.MkDir() 546 stor, err := filestorage.NewFileStorageWriter(storageDir) 547 c.Assert(err, jc.ErrorIsNil) 548 549 uploader := &sync.StorageToolsUploader{ 550 Storage: stor, 551 WriteMetadata: true, 552 WriteMirrors: writeMirrors, 553 } 554 555 err = uploader.UploadTools( 556 "released", 557 "released", 558 &coretools.Tools{ 559 Version: version.Binary{ 560 Number: jujuversion.Current, 561 Arch: arch.HostArch(), 562 Series: series.HostSeries(), 563 }, 564 Size: 7, 565 SHA256: "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73", 566 }, []byte("content")) 567 c.Assert(err, jc.ErrorIsNil) 568 569 mirrorsPath := simplestreams.MirrorsPath(envtools.StreamsVersionV1) + simplestreams.UnsignedSuffix 570 r, err := stor.Get(path.Join(storage.BaseToolsPath, mirrorsPath)) 571 if writeMirrors == envtools.WriteMirrors { 572 c.Assert(err, jc.ErrorIsNil) 573 data, err := ioutil.ReadAll(r) 574 r.Close() 575 c.Assert(err, jc.ErrorIsNil) 576 c.Assert(string(data), jc.Contains, `"mirrors":`) 577 } else { 578 c.Assert(err, jc.Satisfies, errors.IsNotFound) 579 } 580 } 581 582 type mockToolsFinder struct{} 583 584 func (mockToolsFinder) FindTools(major int, stream string) (coretools.List, error) { 585 return nil, coretools.ErrNoMatches 586 }