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