Skip to content

Commit c54d44e

Browse files
committed
tests: cover skel and AppStream publish error paths
Publish moved the skel and AppStream emission out of the per-component loop, and codecov flags the relocated error branches as uncovered. Cover them with deterministic failures: - a regular file in the middle of the skel path makes the walk fail with ENOTDIR, which is not swallowed as a not-exist error - nested skel/AppStream names each within NAME_MAX flatten into a temporary index file name that exceeds it, failing index creation - an unreadable skel file (mode 0) fails the open (non-root only) - a ~1MB filesystem as TMPDIR runs out of space mid-write, reusing the /smallfs convention from DiskFullSuite Also add a unit-level happy path test publishing nested skel files and checking they land in the published tree and the Release checksums.
1 parent bef02fe commit c54d44e

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

‎deb/publish_test.go‎

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import (
66
"errors"
77
"fmt"
88
"os"
9+
"os/exec"
910
"path/filepath"
11+
"runtime"
1012
"sort"
13+
"strings"
1114

1215
"github.com/aptly-dev/aptly/aptly"
1316
"github.com/aptly-dev/aptly/database"
@@ -550,6 +553,165 @@ func (s *PublishedRepoSuite) TestPublishAppStreamAcquireByHash(c *C) {
550553
c.Check(filepath.Join(base, "main/dep11/by-hash/SHA256"), PathExists)
551554
}
552555

556+
// setupSmallTempFS points TMPDIR at a ~1MB filesystem so that Publish's
557+
// temporary index files run out of space mid-write. Follows the DiskFullSuite
558+
// convention: /smallfs is pre-mounted in CI (see docker-unit-test), a loopback
559+
// mount is created when running as root, otherwise the test is skipped.
560+
func (s *PublishedRepoSuite) setupSmallTempFS(c *C) func() {
561+
if runtime.GOOS != "linux" {
562+
c.Skip("disk full tests only run on Linux")
563+
}
564+
565+
mountPoint := "/smallfs"
566+
mounted := false
567+
if os.Geteuid() == 0 {
568+
mountPoint = filepath.Join(c.MkDir(), "smallfs")
569+
c.Assert(os.MkdirAll(mountPoint, 0777), IsNil)
570+
fsImage := filepath.Join(c.MkDir(), "small.img")
571+
c.Assert(exec.Command("dd", "if=/dev/zero", "of="+fsImage, "bs=1M", "count=1").Run(), IsNil)
572+
c.Assert(exec.Command("mkfs.ext4", "-F", fsImage).Run(), IsNil)
573+
c.Assert(exec.Command("mount", "-o", "loop", fsImage, mountPoint).Run(), IsNil)
574+
mounted = true
575+
} else if _, err := os.Stat(mountPoint); err != nil {
576+
c.Skip("/smallfs is not mounted")
577+
}
578+
579+
// remove leftovers from other tests sharing the mount
580+
entries, err := os.ReadDir(mountPoint)
581+
c.Assert(err, IsNil)
582+
for _, entry := range entries {
583+
if entry.Name() == "lost+found" {
584+
continue
585+
}
586+
c.Assert(os.RemoveAll(filepath.Join(mountPoint, entry.Name())), IsNil)
587+
}
588+
589+
oldTMPDIR, hadTMPDIR := os.LookupEnv("TMPDIR")
590+
c.Assert(os.Setenv("TMPDIR", mountPoint), IsNil)
591+
592+
return func() {
593+
if hadTMPDIR {
594+
_ = os.Setenv("TMPDIR", oldTMPDIR)
595+
} else {
596+
_ = os.Unsetenv("TMPDIR")
597+
}
598+
if mounted {
599+
_ = exec.Command("umount", mountPoint).Run()
600+
}
601+
}
602+
}
603+
604+
func (s *PublishedRepoSuite) TestPublishSkelFiles(c *C) {
605+
skelDir := c.MkDir()
606+
dir := filepath.Join(skelDir, "ppa", "dists", "squeeze", "main")
607+
c.Assert(os.MkdirAll(filepath.Join(dir, "extra"), 0755), IsNil)
608+
c.Assert(os.WriteFile(filepath.Join(dir, "top.txt"), []byte("top-level skel file\n"), 0644), IsNil)
609+
c.Assert(os.WriteFile(filepath.Join(dir, "extra", "metadata.json"), []byte("{\"nested\": true}\n"), 0644), IsNil)
610+
611+
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
612+
c.Assert(err, IsNil)
613+
614+
base := filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/main")
615+
616+
published, err := os.ReadFile(filepath.Join(base, "top.txt"))
617+
c.Assert(err, IsNil)
618+
c.Check(string(published), Equals, "top-level skel file\n")
619+
620+
published, err = os.ReadFile(filepath.Join(base, "extra", "metadata.json"))
621+
c.Assert(err, IsNil)
622+
c.Check(string(published), Equals, "{\"nested\": true}\n")
623+
624+
// Release file should reference skel files
625+
rf, err := os.Open(filepath.Join(s.publishedStorage.PublicPath(), "ppa/dists/squeeze/Release"))
626+
c.Assert(err, IsNil)
627+
defer func() { _ = rf.Close() }()
628+
629+
cfr := NewControlFileReader(rf, true, false)
630+
st, err := cfr.ReadStanza()
631+
c.Assert(err, IsNil)
632+
633+
c.Check(st["SHA256"], Matches, "(?s).*main/top\\.txt.*")
634+
c.Check(st["SHA256"], Matches, "(?s).*main/extra/metadata\\.json.*")
635+
}
636+
637+
func (s *PublishedRepoSuite) TestPublishSkelFilesWalkError(c *C) {
638+
if runtime.GOOS == "windows" {
639+
c.Skip("a file in the middle of a path is reported as not-exist on Windows")
640+
}
641+
642+
// "dists" is a regular file, so walking skelDir/ppa/dists/squeeze/main
643+
// fails with ENOTDIR, which is not swallowed as a not-exist error
644+
skelDir := c.MkDir()
645+
c.Assert(os.MkdirAll(filepath.Join(skelDir, "ppa"), 0755), IsNil)
646+
c.Assert(os.WriteFile(filepath.Join(skelDir, "ppa", "dists"), []byte("not a directory"), 0644), IsNil)
647+
648+
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
649+
c.Assert(err, ErrorMatches, "unable to get skeleton files: .*")
650+
}
651+
652+
func (s *PublishedRepoSuite) TestPublishSkelFilesIndexError(c *C) {
653+
// each name stays within NAME_MAX, but BufWriter flattens the relative
654+
// path into a single temporary file name that exceeds it
655+
skelDir := c.MkDir()
656+
dir := filepath.Join(skelDir, "ppa", "dists", "squeeze", "main", strings.Repeat("a", 200))
657+
c.Assert(os.MkdirAll(dir, 0755), IsNil)
658+
c.Assert(os.WriteFile(filepath.Join(dir, strings.Repeat("b", 200)), []byte("skel content"), 0644), IsNil)
659+
660+
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
661+
c.Assert(err, ErrorMatches, "unable to generate skeleton index: .*")
662+
}
663+
664+
func (s *PublishedRepoSuite) TestPublishSkelFilesOpenError(c *C) {
665+
if runtime.GOOS == "windows" || os.Geteuid() == 0 {
666+
c.Skip("requires POSIX permissions and a non-root user")
667+
}
668+
669+
skelDir := c.MkDir()
670+
dir := filepath.Join(skelDir, "ppa", "dists", "squeeze", "main")
671+
c.Assert(os.MkdirAll(dir, 0755), IsNil)
672+
skelFile := filepath.Join(dir, "InRelease")
673+
c.Assert(os.WriteFile(skelFile, []byte("skel content"), 0644), IsNil)
674+
c.Assert(os.Chmod(skelFile, 0), IsNil)
675+
676+
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
677+
c.Assert(err, ErrorMatches, "unable to read skeleton file: .*")
678+
}
679+
680+
func (s *PublishedRepoSuite) TestPublishSkelFilesWriteError(c *C) {
681+
cleanup := s.setupSmallTempFS(c)
682+
defer cleanup()
683+
684+
skelDir := c.MkDir()
685+
dir := filepath.Join(skelDir, "ppa", "dists", "squeeze", "main")
686+
c.Assert(os.MkdirAll(dir, 0755), IsNil)
687+
c.Assert(os.WriteFile(filepath.Join(dir, "Contents-huge"), bytes.Repeat([]byte{'x'}, 2*1024*1024), 0644), IsNil)
688+
689+
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, skelDir)
690+
c.Assert(err, ErrorMatches, "unable to write skeleton file: .*")
691+
}
692+
693+
func (s *PublishedRepoSuite) TestPublishAppStreamIndexError(c *C) {
694+
// flattened temporary file name for the index exceeds NAME_MAX
695+
s.snapshot.AppStreamFiles = map[string]string{
696+
"main/" + strings.Repeat("a", 200) + "/" + strings.Repeat("b", 200): s.importAppStreamFile(c, "Components-amd64.yml.gz", []byte("DEP-11 content\n")),
697+
}
698+
699+
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
700+
c.Assert(err, ErrorMatches, "unable to generate AppStream index: .*")
701+
}
702+
703+
func (s *PublishedRepoSuite) TestPublishAppStreamWriteError(c *C) {
704+
cleanup := s.setupSmallTempFS(c)
705+
defer cleanup()
706+
707+
s.snapshot.AppStreamFiles = map[string]string{
708+
"main/dep11/Components-amd64.yml.gz": s.importAppStreamFile(c, "Components-amd64.yml.gz", bytes.Repeat([]byte{'x'}, 2*1024*1024)),
709+
}
710+
711+
err := s.repo.Publish(s.packagePool, s.provider, s.factory, &NullSigner{}, nil, false, "")
712+
c.Assert(err, ErrorMatches, "unable to write AppStream file: .*")
713+
}
714+
553715
func (s *PublishedRepoSuite) TestPublishNoSigner(c *C) {
554716
err := s.repo.Publish(s.packagePool, s.provider, s.factory, nil, nil, false, "")
555717
c.Assert(err, IsNil)

0 commit comments

Comments
 (0)