github.com/rigado/snapd@v2.42.5-go-mod+incompatible/snap/squashfs/squashfs_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package squashfs_test
    21  
    22  import (
    23  	"errors"
    24  	"io/ioutil"
    25  	"math"
    26  	"os"
    27  	"os/exec"
    28  	"path/filepath"
    29  	"strings"
    30  	"testing"
    31  	"time"
    32  
    33  	. "gopkg.in/check.v1"
    34  	"gopkg.in/yaml.v2"
    35  
    36  	"github.com/snapcore/snapd/dirs"
    37  	"github.com/snapcore/snapd/osutil"
    38  	"github.com/snapcore/snapd/snap/snapdir"
    39  	"github.com/snapcore/snapd/snap/squashfs"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  // Hook up check.v1 into the "go test" runner
    44  func Test(t *testing.T) { TestingT(t) }
    45  
    46  type SquashfsTestSuite struct {
    47  	oldStdout, oldStderr, outf *os.File
    48  }
    49  
    50  var _ = Suite(&SquashfsTestSuite{})
    51  
    52  func makeSnap(c *C, manifest, data string) *squashfs.Snap {
    53  	cur, _ := os.Getwd()
    54  	return makeSnapInDir(c, cur, manifest, data)
    55  }
    56  
    57  func makeSnapContents(c *C, manifest, data string) string {
    58  	tmp := c.MkDir()
    59  	err := os.MkdirAll(filepath.Join(tmp, "meta", "hooks", "dir"), 0755)
    60  	c.Assert(err, IsNil)
    61  
    62  	// our regular snap.yaml
    63  	err = ioutil.WriteFile(filepath.Join(tmp, "meta", "snap.yaml"), []byte(manifest), 0644)
    64  	c.Assert(err, IsNil)
    65  
    66  	// some hooks
    67  	err = ioutil.WriteFile(filepath.Join(tmp, "meta", "hooks", "foo-hook"), nil, 0755)
    68  	c.Assert(err, IsNil)
    69  	err = ioutil.WriteFile(filepath.Join(tmp, "meta", "hooks", "bar-hook"), nil, 0755)
    70  	c.Assert(err, IsNil)
    71  	// And a file in another directory in there, just for testing (not a valid
    72  	// hook)
    73  	err = ioutil.WriteFile(filepath.Join(tmp, "meta", "hooks", "dir", "baz"), nil, 0755)
    74  	c.Assert(err, IsNil)
    75  
    76  	// some empty directories
    77  	err = os.MkdirAll(filepath.Join(tmp, "food", "bard", "bazd"), 0755)
    78  	c.Assert(err, IsNil)
    79  
    80  	// some data
    81  	err = ioutil.WriteFile(filepath.Join(tmp, "data.bin"), []byte(data), 0644)
    82  	c.Assert(err, IsNil)
    83  
    84  	return tmp
    85  }
    86  
    87  func makeSnapInDir(c *C, dir, manifest, data string) *squashfs.Snap {
    88  	snapType := "app"
    89  	var m struct {
    90  		Type string `yaml:"type"`
    91  	}
    92  	if err := yaml.Unmarshal([]byte(manifest), &m); err == nil && m.Type != "" {
    93  		snapType = m.Type
    94  	}
    95  
    96  	tmp := makeSnapContents(c, manifest, data)
    97  	// build it
    98  	snap := squashfs.New(filepath.Join(dir, "foo.snap"))
    99  	err := snap.Build(tmp, snapType)
   100  	c.Assert(err, IsNil)
   101  
   102  	return snap
   103  }
   104  
   105  func (s *SquashfsTestSuite) SetUpTest(c *C) {
   106  	d := c.MkDir()
   107  	dirs.SetRootDir(d)
   108  	err := os.Chdir(d)
   109  	c.Assert(err, IsNil)
   110  
   111  	s.outf, err = ioutil.TempFile(c.MkDir(), "")
   112  	c.Assert(err, IsNil)
   113  	s.oldStdout, s.oldStderr = os.Stdout, os.Stderr
   114  	os.Stdout, os.Stderr = s.outf, s.outf
   115  }
   116  
   117  func (s *SquashfsTestSuite) TearDownTest(c *C) {
   118  	os.Stdout, os.Stderr = s.oldStdout, s.oldStderr
   119  
   120  	// this ensures things were quiet
   121  	_, err := s.outf.Seek(0, 0)
   122  	c.Assert(err, IsNil)
   123  	outbuf, err := ioutil.ReadAll(s.outf)
   124  	c.Assert(err, IsNil)
   125  	c.Check(string(outbuf), Equals, "")
   126  }
   127  
   128  func (s *SquashfsTestSuite) TestInstallSimpleNoCp(c *C) {
   129  	// mock cp but still cp
   130  	cmd := testutil.MockCommand(c, "cp", `#!/bin/sh
   131  exec /bin/cp "$@"
   132  `)
   133  	defer cmd.Restore()
   134  	// mock link but still link
   135  	linked := 0
   136  	r := squashfs.MockLink(func(a, b string) error {
   137  		linked++
   138  		return os.Link(a, b)
   139  	})
   140  	defer r()
   141  
   142  	snap := makeSnap(c, "name: test", "")
   143  	targetPath := filepath.Join(c.MkDir(), "target.snap")
   144  	mountDir := c.MkDir()
   145  	err := snap.Install(targetPath, mountDir)
   146  	c.Assert(err, IsNil)
   147  	c.Check(osutil.FileExists(targetPath), Equals, true)
   148  	c.Check(linked, Equals, 1)
   149  	c.Check(cmd.Calls(), HasLen, 0)
   150  }
   151  
   152  func noLink() func() {
   153  	return squashfs.MockLink(func(string, string) error { return errors.New("no.") })
   154  }
   155  
   156  func (s *SquashfsTestSuite) TestInstallNotCopyTwice(c *C) {
   157  	// first, disable os.Link
   158  	defer noLink()()
   159  
   160  	// then, mock cp but still cp
   161  	cmd := testutil.MockCommand(c, "cp", `#!/bin/sh
   162  exec /bin/cp "$@"
   163  `)
   164  	defer cmd.Restore()
   165  
   166  	snap := makeSnap(c, "name: test2", "")
   167  	targetPath := filepath.Join(c.MkDir(), "target.snap")
   168  	mountDir := c.MkDir()
   169  	err := snap.Install(targetPath, mountDir)
   170  	c.Assert(err, IsNil)
   171  	c.Check(cmd.Calls(), HasLen, 1)
   172  
   173  	err = snap.Install(targetPath, mountDir)
   174  	c.Assert(err, IsNil)
   175  	c.Check(cmd.Calls(), HasLen, 1) // and not 2 \o/
   176  }
   177  
   178  func (s *SquashfsTestSuite) TestInstallSeedNoLink(c *C) {
   179  	defer noLink()()
   180  
   181  	c.Assert(os.MkdirAll(dirs.SnapSeedDir, 0755), IsNil)
   182  	snap := makeSnapInDir(c, dirs.SnapSeedDir, "name: test2", "")
   183  	targetPath := filepath.Join(c.MkDir(), "target.snap")
   184  	_, err := os.Lstat(targetPath)
   185  	c.Check(os.IsNotExist(err), Equals, true)
   186  
   187  	err = snap.Install(targetPath, c.MkDir())
   188  	c.Assert(err, IsNil)
   189  	c.Check(osutil.IsSymlink(targetPath), Equals, true) // \o/
   190  }
   191  
   192  func (s *SquashfsTestSuite) TestPath(c *C) {
   193  	p := "/path/to/foo.snap"
   194  	snap := squashfs.New("/path/to/foo.snap")
   195  	c.Assert(snap.Path(), Equals, p)
   196  }
   197  
   198  func (s *SquashfsTestSuite) TestReadFile(c *C) {
   199  	snap := makeSnap(c, "name: foo", "")
   200  
   201  	content, err := snap.ReadFile("meta/snap.yaml")
   202  	c.Assert(err, IsNil)
   203  	c.Assert(string(content), Equals, "name: foo")
   204  }
   205  
   206  func (s *SquashfsTestSuite) TestListDir(c *C) {
   207  	snap := makeSnap(c, "name: foo", "")
   208  
   209  	fileNames, err := snap.ListDir("meta/hooks")
   210  	c.Assert(err, IsNil)
   211  	c.Assert(len(fileNames), Equals, 3)
   212  	c.Check(fileNames[0], Equals, "bar-hook")
   213  	c.Check(fileNames[1], Equals, "dir")
   214  	c.Check(fileNames[2], Equals, "foo-hook")
   215  }
   216  
   217  func (s *SquashfsTestSuite) TestWalk(c *C) {
   218  	sub := "."
   219  	snap := makeSnap(c, "name: foo", "")
   220  	sqw := map[string]os.FileInfo{}
   221  	snap.Walk(sub, func(path string, info os.FileInfo, err error) error {
   222  		if err != nil {
   223  			return err
   224  		}
   225  		if path == "food" {
   226  			return filepath.SkipDir
   227  		}
   228  		sqw[path] = info
   229  		return nil
   230  	})
   231  
   232  	base := c.MkDir()
   233  	c.Assert(snap.Unpack("*", base), IsNil)
   234  
   235  	sdw := map[string]os.FileInfo{}
   236  	snapdir.New(base).Walk(sub, func(path string, info os.FileInfo, err error) error {
   237  		if err != nil {
   238  			return err
   239  		}
   240  		if path == "food" {
   241  			return filepath.SkipDir
   242  		}
   243  		sdw[path] = info
   244  		return nil
   245  	})
   246  
   247  	fpw := map[string]os.FileInfo{}
   248  	filepath.Walk(filepath.Join(base, sub), func(path string, info os.FileInfo, err error) error {
   249  		if err != nil {
   250  			return err
   251  		}
   252  		path, err = filepath.Rel(base, path)
   253  		if err != nil {
   254  			return err
   255  		}
   256  		if path == "food" {
   257  			return filepath.SkipDir
   258  		}
   259  		fpw[path] = info
   260  		return nil
   261  	})
   262  
   263  	for k := range fpw {
   264  		squashfs.Alike(sqw[k], fpw[k], c, Commentf(k))
   265  		squashfs.Alike(sdw[k], fpw[k], c, Commentf(k))
   266  	}
   267  
   268  	for k := range sqw {
   269  		squashfs.Alike(fpw[k], sqw[k], c, Commentf(k))
   270  		squashfs.Alike(sdw[k], sqw[k], c, Commentf(k))
   271  	}
   272  
   273  	for k := range sdw {
   274  		squashfs.Alike(fpw[k], sdw[k], c, Commentf(k))
   275  		squashfs.Alike(sqw[k], sdw[k], c, Commentf(k))
   276  	}
   277  
   278  }
   279  
   280  // TestUnpackGlob tests the internal unpack
   281  func (s *SquashfsTestSuite) TestUnpackGlob(c *C) {
   282  	data := "some random data"
   283  	snap := makeSnap(c, "", data)
   284  
   285  	outputDir := c.MkDir()
   286  	err := snap.Unpack("data*", outputDir)
   287  	c.Assert(err, IsNil)
   288  
   289  	// this is the file we expect
   290  	c.Assert(filepath.Join(outputDir, "data.bin"), testutil.FileEquals, data)
   291  
   292  	// ensure glob was honored
   293  	c.Assert(osutil.FileExists(filepath.Join(outputDir, "meta/snap.yaml")), Equals, false)
   294  }
   295  
   296  func (s *SquashfsTestSuite) TestUnpackDetectsFailures(c *C) {
   297  	mockUnsquashfs := testutil.MockCommand(c, "unsquashfs", `
   298  cat >&2 <<EOF
   299  Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols, skipping
   300  
   301  Write on output file failed because No space left on device
   302  
   303  writer: failed to write data block 0
   304  
   305  Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols.bin, skipping
   306  
   307  Write on output file failed because No space left on device
   308  
   309  writer: failed to write data block 0
   310  
   311  Failed to write /tmp/1/modules/4.4.0-112-generic/vdso/vdso32.so, skipping
   312  
   313  Write on output file failed because No space left on device
   314  
   315  writer: failed to write data block 0
   316  
   317  Failed to write /tmp/1/modules/4.4.0-112-generic/vdso/vdso64.so, skipping
   318  
   319  Write on output file failed because No space left on device
   320  
   321  writer: failed to write data block 0
   322  
   323  Failed to write /tmp/1/modules/4.4.0-112-generic/vdso/vdsox32.so, skipping
   324  
   325  Write on output file failed because No space left on device
   326  
   327  writer: failed to write data block 0
   328  
   329  Failed to write /tmp/1/snap/manifest.yaml, skipping
   330  
   331  Write on output file failed because No space left on device
   332  
   333  writer: failed to write data block 0
   334  
   335  Failed to write /tmp/1/snap/snapcraft.yaml, skipping
   336  EOF
   337  `)
   338  	defer mockUnsquashfs.Restore()
   339  
   340  	data := "mock kernel snap"
   341  	snap := makeSnap(c, "", data)
   342  	err := snap.Unpack("*", "some-output-dir")
   343  	c.Assert(err, NotNil)
   344  	c.Check(err.Error(), Equals, `cannot extract "*" to "some-output-dir": failed: "Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols, skipping", "Write on output file failed because No space left on device", "writer: failed to write data block 0", "Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols.bin, skipping", and 15 more`)
   345  }
   346  
   347  func (s *SquashfsTestSuite) TestBuild(c *C) {
   348  	// please keep TestBuildUsesExcludes in sync with this one so it makes sense.
   349  	buildDir := c.MkDir()
   350  	err := os.MkdirAll(filepath.Join(buildDir, "/random/dir"), 0755)
   351  	c.Assert(err, IsNil)
   352  	err = ioutil.WriteFile(filepath.Join(buildDir, "data.bin"), []byte("data"), 0644)
   353  	c.Assert(err, IsNil)
   354  	err = ioutil.WriteFile(filepath.Join(buildDir, "random", "data.bin"), []byte("more data"), 0644)
   355  	c.Assert(err, IsNil)
   356  
   357  	snap := squashfs.New(filepath.Join(c.MkDir(), "foo.snap"))
   358  	err = snap.Build(buildDir, "app")
   359  	c.Assert(err, IsNil)
   360  
   361  	// unsquashfs writes a funny header like:
   362  	//     "Parallel unsquashfs: Using 1 processor"
   363  	//     "1 inodes (1 blocks) to write"
   364  	outputWithHeader, err := exec.Command("unsquashfs", "-n", "-l", snap.Path()).Output()
   365  	c.Assert(err, IsNil)
   366  	split := strings.Split(string(outputWithHeader), "\n")
   367  	output := strings.Join(split[3:], "\n")
   368  	c.Assert(string(output), Equals, `
   369  squashfs-root
   370  squashfs-root/data.bin
   371  squashfs-root/random
   372  squashfs-root/random/data.bin
   373  squashfs-root/random/dir
   374  `[1:]) // skip the first newline :-)
   375  }
   376  
   377  func (s *SquashfsTestSuite) TestBuildUsesExcludes(c *C) {
   378  	// please keep TestBuild in sync with this one so it makes sense.
   379  	buildDir := c.MkDir()
   380  	err := os.MkdirAll(filepath.Join(buildDir, "/random/dir"), 0755)
   381  	c.Assert(err, IsNil)
   382  	err = ioutil.WriteFile(filepath.Join(buildDir, "data.bin"), []byte("data"), 0644)
   383  	c.Assert(err, IsNil)
   384  	err = ioutil.WriteFile(filepath.Join(buildDir, "random", "data.bin"), []byte("more data"), 0644)
   385  	c.Assert(err, IsNil)
   386  
   387  	excludesFilename := filepath.Join(buildDir, ".snapignore")
   388  	err = ioutil.WriteFile(excludesFilename, []byte(`
   389  # ignore just one of the data.bin files we just added (the toplevel one)
   390  data.bin
   391  # also ignore ourselves
   392  .snapignore
   393  # oh and anything called "dir" anywhere
   394  ... dir
   395  `), 0644)
   396  	c.Assert(err, IsNil)
   397  
   398  	snap := squashfs.New(filepath.Join(c.MkDir(), "foo.snap"))
   399  	err = snap.Build(buildDir, "app", excludesFilename)
   400  	c.Assert(err, IsNil)
   401  
   402  	outputWithHeader, err := exec.Command("unsquashfs", "-n", "-l", snap.Path()).Output()
   403  	c.Assert(err, IsNil)
   404  	split := strings.Split(string(outputWithHeader), "\n")
   405  	output := strings.Join(split[3:], "\n")
   406  	// compare with TestBuild
   407  	c.Assert(string(output), Equals, `
   408  squashfs-root
   409  squashfs-root/random
   410  squashfs-root/random/data.bin
   411  `[1:]) // skip the first newline :-)
   412  }
   413  
   414  func (s *SquashfsTestSuite) TestBuildSupportsMultipleExcludesWithOnlyOneWildcardsFlag(c *C) {
   415  	defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) {
   416  		c.Check(cmd, Equals, "/usr/bin/mksquashfs")
   417  		return nil, errors.New("bzzt")
   418  	})()
   419  	mksq := testutil.MockCommand(c, "mksquashfs", "")
   420  	defer mksq.Restore()
   421  
   422  	snapPath := filepath.Join(c.MkDir(), "foo.snap")
   423  	snap := squashfs.New(snapPath)
   424  	err := snap.Build(c.MkDir(), "core", "exclude1", "exclude2", "exclude3")
   425  	c.Assert(err, IsNil)
   426  	calls := mksq.Calls()
   427  	c.Assert(calls, HasLen, 1)
   428  	c.Check(calls[0], DeepEquals, []string{
   429  		// the usual:
   430  		"mksquashfs", ".", snapPath, "-noappend", "-comp", "xz", "-no-fragments", "-no-progress",
   431  		// the interesting bits:
   432  		"-wildcards", "-ef", "exclude1", "-ef", "exclude2", "-ef", "exclude3",
   433  	})
   434  }
   435  
   436  func (s *SquashfsTestSuite) TestBuildUsesMksquashfsFromCoreIfAvailable(c *C) {
   437  	usedFromCore := false
   438  	defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) {
   439  		usedFromCore = true
   440  		c.Check(cmd, Equals, "/usr/bin/mksquashfs")
   441  		return &exec.Cmd{Path: "/bin/true"}, nil
   442  	})()
   443  	mksq := testutil.MockCommand(c, "mksquashfs", "exit 1")
   444  	defer mksq.Restore()
   445  
   446  	buildDir := c.MkDir()
   447  
   448  	snap := squashfs.New(filepath.Join(c.MkDir(), "foo.snap"))
   449  	err := snap.Build(buildDir, "app")
   450  	c.Assert(err, IsNil)
   451  	c.Check(usedFromCore, Equals, true)
   452  	c.Check(mksq.Calls(), HasLen, 0)
   453  }
   454  
   455  func (s *SquashfsTestSuite) TestBuildUsesMksquashfsFromClassicIfCoreUnavailable(c *C) {
   456  	triedFromCore := false
   457  	defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) {
   458  		triedFromCore = true
   459  		c.Check(cmd, Equals, "/usr/bin/mksquashfs")
   460  		return nil, errors.New("bzzt")
   461  	})()
   462  	mksq := testutil.MockCommand(c, "mksquashfs", "")
   463  	defer mksq.Restore()
   464  
   465  	buildDir := c.MkDir()
   466  
   467  	snap := squashfs.New(filepath.Join(c.MkDir(), "foo.snap"))
   468  	err := snap.Build(buildDir, "app")
   469  	c.Assert(err, IsNil)
   470  	c.Check(triedFromCore, Equals, true)
   471  	c.Check(mksq.Calls(), HasLen, 1)
   472  }
   473  
   474  func (s *SquashfsTestSuite) TestBuildFailsIfNoMksquashfs(c *C) {
   475  	triedFromCore := false
   476  	defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) {
   477  		triedFromCore = true
   478  		c.Check(cmd, Equals, "/usr/bin/mksquashfs")
   479  		return nil, errors.New("bzzt")
   480  	})()
   481  	mksq := testutil.MockCommand(c, "mksquashfs", "exit 1")
   482  	defer mksq.Restore()
   483  
   484  	buildDir := c.MkDir()
   485  
   486  	snap := squashfs.New(filepath.Join(c.MkDir(), "foo.snap"))
   487  	err := snap.Build(buildDir, "app")
   488  	c.Assert(err, ErrorMatches, "mksquashfs call failed:.*")
   489  	c.Check(triedFromCore, Equals, true)
   490  	c.Check(mksq.Calls(), HasLen, 1)
   491  }
   492  
   493  func (s *SquashfsTestSuite) TestBuildVariesArgsByType(c *C) {
   494  	defer squashfs.MockCommandFromSystemSnap(func(cmd string, args ...string) (*exec.Cmd, error) {
   495  		return nil, errors.New("bzzt")
   496  	})()
   497  	mksq := testutil.MockCommand(c, "mksquashfs", "")
   498  	defer mksq.Restore()
   499  
   500  	buildDir := c.MkDir()
   501  	filename := filepath.Join(c.MkDir(), "foo.snap")
   502  	snap := squashfs.New(filename)
   503  
   504  	permissiveTypeArgs := []string{".", filename, "-noappend", "-comp", "xz", "-no-fragments", "-no-progress"}
   505  	restrictedTypeArgs := append(permissiveTypeArgs, "-all-root", "-no-xattrs")
   506  	tests := []struct {
   507  		snapType string
   508  		args     []string
   509  	}{
   510  		{"", restrictedTypeArgs},
   511  		{"app", restrictedTypeArgs},
   512  		{"gadget", restrictedTypeArgs},
   513  		{"kernel", restrictedTypeArgs},
   514  		{"snapd", restrictedTypeArgs},
   515  		{"base", permissiveTypeArgs},
   516  		{"os", permissiveTypeArgs},
   517  		{"core", permissiveTypeArgs},
   518  	}
   519  
   520  	for _, t := range tests {
   521  		mksq.ForgetCalls()
   522  		comm := Commentf("type: %s", t.snapType)
   523  
   524  		c.Check(snap.Build(buildDir, t.snapType), IsNil, comm)
   525  		c.Assert(mksq.Calls(), HasLen, 1, comm)
   526  		c.Assert(mksq.Calls()[0], HasLen, len(t.args)+1)
   527  		c.Check(mksq.Calls()[0][0], Equals, "mksquashfs", comm)
   528  		c.Check(mksq.Calls()[0][1:], DeepEquals, t.args, comm)
   529  	}
   530  }
   531  
   532  func (s *SquashfsTestSuite) TestBuildReportsFailures(c *C) {
   533  	mockUnsquashfs := testutil.MockCommand(c, "mksquashfs", `
   534  echo Yeah, nah. >&2
   535  exit 1
   536  `)
   537  	defer mockUnsquashfs.Restore()
   538  
   539  	data := "mock kernel snap"
   540  	dir := makeSnapContents(c, "", data)
   541  	snap := squashfs.New("foo.snap")
   542  	c.Check(snap.Build(dir, "kernel"), ErrorMatches, `mksquashfs call failed: Yeah, nah.`)
   543  }
   544  
   545  func (s *SquashfsTestSuite) TestUnsquashfsStderrWriter(c *C) {
   546  	for _, t := range []struct {
   547  		inp         []string
   548  		expectedErr string
   549  	}{
   550  		{
   551  			inp:         []string{"failed to write something\n"},
   552  			expectedErr: `failed: "failed to write something"`,
   553  		},
   554  		{
   555  			inp:         []string{"fai", "led to write", " something\nunrelated\n"},
   556  			expectedErr: `failed: "failed to write something"`,
   557  		},
   558  		{
   559  			inp:         []string{"failed to write\nfailed to read\n"},
   560  			expectedErr: `failed: "failed to write", and "failed to read"`,
   561  		},
   562  		{
   563  			inp:         []string{"failed 1\nfailed 2\n3 failed\n"},
   564  			expectedErr: `failed: "failed 1", "failed 2", and "3 failed"`,
   565  		},
   566  		{
   567  			inp:         []string{"failed 1\nfailed 2\n3 Failed\n4 Failed\n"},
   568  			expectedErr: `failed: "failed 1", "failed 2", "3 Failed", and "4 Failed"`,
   569  		},
   570  		{
   571  			inp:         []string{"failed 1\nfailed 2\n3 Failed\n4 Failed\nfailed #5\n"},
   572  			expectedErr: `failed: "failed 1", "failed 2", "3 Failed", "4 Failed", and 1 more`,
   573  		},
   574  	} {
   575  		usw := squashfs.NewUnsquashfsStderrWriter()
   576  		for _, l := range t.inp {
   577  			usw.Write([]byte(l))
   578  		}
   579  		if t.expectedErr != "" {
   580  			c.Check(usw.Err(), ErrorMatches, t.expectedErr, Commentf("inp: %q failed", t.inp))
   581  		} else {
   582  			c.Check(usw.Err(), IsNil)
   583  		}
   584  	}
   585  }
   586  
   587  func (s *SquashfsTestSuite) TestBuildDate(c *C) {
   588  	// This env is used in reproducible builds and will force
   589  	// squashfs to use a specific date. We need to unset it
   590  	// for this specific test.
   591  	if oldEnv := os.Getenv("SOURCE_DATE_EPOCH"); oldEnv != "" {
   592  		os.Unsetenv("SOURCE_DATE_EPOCH")
   593  		defer func() { os.Setenv("SOURCE_DATE_EPOCH", oldEnv) }()
   594  	}
   595  
   596  	// make a directory
   597  	d := c.MkDir()
   598  	// set its time waaay back
   599  	now := time.Now()
   600  	then := now.Add(-10000 * time.Hour)
   601  	c.Assert(os.Chtimes(d, then, then), IsNil)
   602  	// make a snap using this directory
   603  	filename := filepath.Join(c.MkDir(), "foo.snap")
   604  	snap := squashfs.New(filename)
   605  	c.Assert(snap.Build(d, "app"), IsNil)
   606  	// and see it's BuildDate is _now_, not _then_.
   607  	c.Check(squashfs.BuildDate(filename), Equals, snap.BuildDate())
   608  	c.Check(math.Abs(now.Sub(snap.BuildDate()).Seconds()) <= 61, Equals, true, Commentf("Unexpected build date %s", snap.BuildDate()))
   609  }