aboutsummaryrefslogtreecommitdiffhomepage
path: root/test/internal
diff options
context:
space:
mode:
Diffstat (limited to 'test/internal')
-rw-r--r--test/internal/mountinfo/mountinfo.go177
-rw-r--r--test/internal/mountinfo/mountinfo_guard.go15
-rw-r--r--test/internal/mountinfo/mountinfo_test.go146
-rw-r--r--test/internal/testsuite/fs.go129
-rw-r--r--test/internal/testsuite/fs_test.go85
-rw-r--r--test/internal/testsuite/proc.go353
-rw-r--r--test/internal/testsuite/proc_test.go33
-rw-r--r--test/internal/testsuite/ptrace.go144
-rw-r--r--test/internal/testsuite/ptrace_test.go13
-rw-r--r--test/internal/testsuite/testsuite.go290
-rw-r--r--test/internal/testsuite/testsuite_guard.go15
-rw-r--r--test/internal/testsuite/testsuite_root.go17
12 files changed, 0 insertions, 1417 deletions
diff --git a/test/internal/mountinfo/mountinfo.go b/test/internal/mountinfo/mountinfo.go
deleted file mode 100644
index 19892cd2..00000000
--- a/test/internal/mountinfo/mountinfo.go
+++ /dev/null
@@ -1,177 +0,0 @@
-// Package mountinfo provides util-linux bindings for parsing
-// proc_pid_mountinfo(5).
-//
-// This package must never be used outside integration tests, a much better
-// implementation can be found in package vfs.
-//
-// Attempting to import this package outside testing causes the resulting
-// program to panic.
-package mountinfo
-
-/*
-#cgo linux pkg-config: --static mount
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <libmount.h>
-
-const char *HAKUREI_MOUNTINFO_PATH = "/proc/self/mountinfo";
-*/
-import "C"
-
-import (
- "errors"
- "fmt"
- "runtime"
- "unsafe"
-)
-
-var (
- // ErrParse is returned by [Open] when encountering a bad record.
- ErrParse = errors.New("invalid mountinfo record")
- // ErrIter is returned by [Open] if an iterator cannot be allocated.
- ErrIter = errors.New("cannot allocate iterator")
- // ErrIterAdvance is stored when the iterator is unable to advance.
- ErrIterAdvance = errors.New("unable to advance iterator")
-)
-
-type (
- // Iter refers to libmnt iterator state.
- Iter struct {
- // Last stored error.
- err error
- // Whether iteration has concluded.
- ok bool
- // Whether Close had already been called.
- closed bool
-
- tb *C.struct_libmnt_table
- itr *C.struct_libmnt_iter
-
- fs *C.struct_libmnt_fs
- }
-
- // Entry represents deterministic mountinfo parts of a libmnt_fs entry.
- Entry struct {
- // mount ID: a unique ID for the mount (may be reused after umount(2)).
- ID int `json:"id"`
- // parent ID: the ID of the parent mount (or of self for the root of
- // this mount namespace's mount tree).
- Parent int `json:"parent"`
- // root: the pathname of the directory in the filesystem which forms the
- // root of this mount.
- Root string `json:"root"`
- // mount point: the pathname of the mount point relative to the
- // process's root directory.
- Target string `json:"target"`
- // mount options: per-mount options (see mount(2)).
- VfsOptstr string `json:"vfs_optstr"`
- // filesystem type: the filesystem type in the form "type[.subtype]".
- FsType string `json:"fstype"`
- // mount source: filesystem-specific information or "none".
- Source string `json:"source"`
- // super options: per-superblock options (see mount(2)).
- FsOptstr string `json:"fs_optstr"`
- }
-)
-
-// Copy populates v with the current record.
-func (m *Iter) Copy(v *Entry) {
- if m.fs == nil {
- panic("invalid entry")
- }
- v.ID = int(C.mnt_fs_get_id(m.fs))
- v.Parent = int(C.mnt_fs_get_parent_id(m.fs))
- v.Root = C.GoString(C.mnt_fs_get_root(m.fs))
- v.Target = C.GoString(C.mnt_fs_get_target(m.fs))
- v.VfsOptstr = C.GoString(C.mnt_fs_get_vfs_options(m.fs))
- v.FsType = C.GoString(C.mnt_fs_get_fstype(m.fs))
- v.Source = C.GoString(C.mnt_fs_get_source(m.fs))
- v.FsOptstr = C.GoString(C.mnt_fs_get_fs_options(m.fs))
-}
-
-// Err returns the saved iterator error.
-func (m *Iter) Err() error { return m.err }
-
-// Open opens a mountinfo document. If name is an empty string, the mountinfo
-// document of the current process is opened instead.
-func Open(name string) (*Iter, error) {
- var m Iter
- if name == "" {
- m.tb = C.mnt_new_table_from_file(C.HAKUREI_MOUNTINFO_PATH)
- } else {
- _name := C.CString(name)
- m.tb = C.mnt_new_table_from_file(_name)
- C.free(unsafe.Pointer(_name))
- }
- if m.tb == nil {
- return nil, ErrParse
- }
- m.itr = C.mnt_new_iter(C.MNT_ITER_FORWARD)
- if m.itr == nil {
- C.mnt_unref_table(m.tb)
- return nil, ErrIter
- }
- m.ok = true
-
- runtime.SetFinalizer(&m, (*Iter).Close)
- return &m, nil
-}
-
-// Close frees the iterator.
-func (m *Iter) Close() {
- if m.closed {
- return
- }
- if m.tb == nil {
- panic("unref called before open")
- }
-
- C.mnt_unref_table(m.tb)
- C.mnt_free_iter(m.itr)
- m.closed = true
- runtime.SetFinalizer(m, nil)
-}
-
-// Reset resets the iterator to the first record for reuse.
-func (m *Iter) Reset() {
- if m.err != nil {
- panic("attempting to reset a faulted iterator")
- }
- m.ok = true
- C.mnt_reset_iter(m.itr, -1)
-}
-
-// Next advances the iterator to the next record. The record may be copied if
-// Next returns true.
-func (m *Iter) Next() bool {
- if !m.ok || m.err != nil {
- return false
- }
-
- r := C.mnt_table_next_fs(m.tb, m.itr, &m.fs)
- if r < 0 {
- m.err = ErrIterAdvance
- }
- m.ok = r == 0
- return m.ok
-}
-
-// EqualWithIgnore compares e with want, ignoring fields with the specified
-// ignore value.
-func (e *Entry) EqualWithIgnore(want *Entry, ignore string) bool {
- return (e.ID == want.ID || want.ID == -1) &&
- (e.Parent == want.Parent || want.Parent == -1) &&
- (e.Root == want.Root || want.Root == ignore) &&
- (e.Target == want.Target || want.Target == ignore) &&
- (e.VfsOptstr == want.VfsOptstr || want.VfsOptstr == ignore) &&
- (e.FsType == want.FsType || want.FsType == ignore) &&
- (e.Source == want.Source || want.Source == ignore) &&
- (e.FsOptstr == want.FsOptstr || want.FsOptstr == ignore)
-}
-
-// String returns a text representation of e loosely following the kernel format.
-func (e *Entry) String() string {
- return fmt.Sprintf("%d %d %s %s %s %s %s %s",
- e.ID, e.Parent, e.Root, e.Target, e.VfsOptstr, e.FsType, e.Source, e.FsOptstr)
-}
diff --git a/test/internal/mountinfo/mountinfo_guard.go b/test/internal/mountinfo/mountinfo_guard.go
deleted file mode 100644
index aaf63ac4..00000000
--- a/test/internal/mountinfo/mountinfo_guard.go
+++ /dev/null
@@ -1,15 +0,0 @@
-//go:build !testsuite && !tester
-
-package mountinfo
-
-import (
- "os"
- "testing"
-)
-
-func init() {
- if !testing.Testing() {
- println("package mountinfo imported in non-testsuite program")
- os.Exit(1)
- }
-}
diff --git a/test/internal/mountinfo/mountinfo_test.go b/test/internal/mountinfo/mountinfo_test.go
deleted file mode 100644
index 45cfdf5f..00000000
--- a/test/internal/mountinfo/mountinfo_test.go
+++ /dev/null
@@ -1,146 +0,0 @@
-package mountinfo_test
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "hakurei.app/test/internal/mountinfo"
-)
-
-func TestMountinfo(t *testing.T) {
- testCases := []struct {
- name string
-
- sample string
- want []*mountinfo.Entry
- }{
- {"util-linux", `15 20 0:3 / /proc rw,relatime - proc /proc rw
-16 20 0:15 / /sys rw,relatime - sysfs /sys rw
-17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=1983516k,nr_inodes=495879,mode=755
-18 17 0:10 / /dev/pts rw,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
-19 17 0:16 / /dev/shm rw,relatime - tmpfs tmpfs rw
-20 1 8:4 / / rw,noatime - ext3 /dev/sda4 rw,errors=continue,user_xattr,acl,barrier=0,data=ordered
-21 16 0:17 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755
-22 21 0:18 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
-23 21 0:19 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset
-24 21 0:20 / /sys/fs/cgroup/ns rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,ns
-25 21 0:21 / /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu
-26 21 0:22 / /sys/fs/cgroup/cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuacct
-27 21 0:23 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory
-28 21 0:24 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices
-29 21 0:25 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer
-30 21 0:26 / /sys/fs/cgroup/net_cls rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,net_cls
-31 21 0:27 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio
-32 16 0:28 / /sys/kernel/security rw,relatime - autofs systemd-1 rw,fd=22,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-33 17 0:29 / /dev/hugepages rw,relatime - autofs systemd-1 rw,fd=23,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-34 16 0:30 / /sys/kernel/debug rw,relatime - autofs systemd-1 rw,fd=24,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-35 15 0:31 / /proc/sys/fs/binfmt_misc rw,relatime - autofs systemd-1 rw,fd=25,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-36 17 0:32 / /dev/mqueue rw,relatime - autofs systemd-1 rw,fd=26,pgrp=1,timeout=300,minproto=5,maxproto=5,direct
-37 15 0:14 / /proc/bus/usb rw,relatime - usbfs /proc/bus/usb rw
-38 33 0:33 / /dev/hugepages rw,relatime - hugetlbfs hugetlbfs rw
-39 36 0:12 / /dev/mqueue rw,relatime - mqueue mqueue rw
-40 20 8:6 / /boot rw,noatime - ext3 /dev/sda6 rw,errors=continue,barrier=0,data=ordered
-41 20 253:0 / /home/kzak rw,noatime - ext4 /dev/mapper/kzak-home rw,barrier=1,data=ordered
-42 35 0:34 / /proc/sys/fs/binfmt_misc rw,relatime - binfmt_misc none rw
-43 16 0:35 / /sys/fs/fuse/connections rw,relatime - fusectl fusectl rw
-44 41 0:36 / /home/kzak/.gvfs rw,nosuid,nodev,relatime - fuse.gvfs-fuse-daemon gvfs-fuse-daemon rw,user_id=500,group_id=500
-45 20 0:37 / /var/lib/nfs/rpc_pipefs rw,relatime - rpc_pipefs sunrpc rw
-47 20 0:38 / /mnt/sounds rw,relatime - cifs //foo.home/bar/ rw,unc=\\foo.home\bar,username=kzak,domain=SRGROUP,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.111.1,posixpaths,serverino,acl,rsize=16384,wsize=57344
-49 20 0:56 / /mnt/test/foobar rw,relatime,nosymfollow shared:323 - tmpfs tmpfs rw`, []*mountinfo.Entry{
- e(15, 20, "/", "/proc", "rw,relatime", "proc", "/proc", "rw"),
- e(16, 20, "/", "/sys", "rw,relatime", "sysfs", "/sys", "rw"),
- e(17, 20, "/", "/dev", "rw,relatime", "devtmpfs", "udev", "rw,size=1983516k,nr_inodes=495879,mode=755"),
- e(18, 17, "/", "/dev/pts", "rw,relatime", "devpts", "devpts", "rw,gid=5,mode=620,ptmxmode=000"),
- e(19, 17, "/", "/dev/shm", "rw,relatime", "tmpfs", "tmpfs", "rw"),
- e(20, 1, "/", "/", "rw,noatime", "ext3", "/dev/sda4", "rw,errors=continue,user_xattr,acl,barrier=0,data=ordered"),
- e(21, 16, "/", "/sys/fs/cgroup", "rw,nosuid,nodev,noexec,relatime", "tmpfs", "tmpfs", "rw,mode=755"),
- e(22, 21, "/", "/sys/fs/cgroup/systemd", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd"),
- e(23, 21, "/", "/sys/fs/cgroup/cpuset", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,cpuset"),
- e(24, 21, "/", "/sys/fs/cgroup/ns", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,ns"),
- e(25, 21, "/", "/sys/fs/cgroup/cpu", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,cpu"),
- e(26, 21, "/", "/sys/fs/cgroup/cpuacct", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,cpuacct"),
- e(27, 21, "/", "/sys/fs/cgroup/memory", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,memory"),
- e(28, 21, "/", "/sys/fs/cgroup/devices", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,devices"),
- e(29, 21, "/", "/sys/fs/cgroup/freezer", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,freezer"),
- e(30, 21, "/", "/sys/fs/cgroup/net_cls", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,net_cls"),
- e(31, 21, "/", "/sys/fs/cgroup/blkio", "rw,nosuid,nodev,noexec,relatime", "cgroup", "cgroup", "rw,blkio"),
- e(32, 16, "/", "/sys/kernel/security", "rw,relatime", "autofs", "systemd-1", "rw,fd=22,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(33, 17, "/", "/dev/hugepages", "rw,relatime", "autofs", "systemd-1", "rw,fd=23,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(34, 16, "/", "/sys/kernel/debug", "rw,relatime", "autofs", "systemd-1", "rw,fd=24,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(35, 15, "/", "/proc/sys/fs/binfmt_misc", "rw,relatime", "autofs", "systemd-1", "rw,fd=25,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(36, 17, "/", "/dev/mqueue", "rw,relatime", "autofs", "systemd-1", "rw,fd=26,pgrp=1,timeout=300,minproto=5,maxproto=5,direct"),
- e(37, 15, "/", "/proc/bus/usb", "rw,relatime", "usbfs", "/proc/bus/usb", "rw"),
- e(38, 33, "/", "/dev/hugepages", "rw,relatime", "hugetlbfs", "hugetlbfs", "rw"),
- e(39, 36, "/", "/dev/mqueue", "rw,relatime", "mqueue", "mqueue", "rw"),
- e(40, 20, "/", "/boot", "rw,noatime", "ext3", "/dev/sda6", "rw,errors=continue,barrier=0,data=ordered"),
- e(41, 20, "/", "/home/kzak", "rw,noatime", "ext4", "/dev/mapper/kzak-home", "rw,barrier=1,data=ordered"),
- e(42, 35, "/", "/proc/sys/fs/binfmt_misc", "rw,relatime", "binfmt_misc", "none", "rw"),
- e(43, 16, "/", "/sys/fs/fuse/connections", "rw,relatime", "fusectl", "fusectl", "rw"),
- e(44, 41, "/", "/home/kzak/.gvfs", "rw,nosuid,nodev,relatime", "fuse.gvfs-fuse-daemon", "gvfs-fuse-daemon", "rw,user_id=500,group_id=500"),
- e(45, 20, "/", "/var/lib/nfs/rpc_pipefs", "rw,relatime", "rpc_pipefs", "sunrpc", "rw"),
- e(47, 20, "/", "/mnt/sounds", "rw,relatime", "cifs", "//foo.home/bar/", "rw,unc=\\\\foo.home\\bar,username=kzak,domain=SRGROUP,uid=0,noforceuid,gid=0,noforcegid,addr=192.168.111.1,posixpaths,serverino,acl,rsize=16384,wsize=57344"),
- e(49, 20, "/", "/mnt/test/foobar", "rw,relatime,nosymfollow", "tmpfs", "tmpfs", "rw"),
- }},
- }
-
- for _, tc := range testCases {
- name := filepath.Join(t.TempDir(), "sample")
- if err := os.WriteFile(name, []byte(tc.sample), 0400); err != nil {
- t.Fatalf("cannot write sample: %v", err)
- }
-
- t.Run(tc.name, func(t *testing.T) {
- m, err := mountinfo.Open(name)
- if err != nil {
- t.Fatalf("Open: error = %v", err)
- }
- t.Cleanup(m.Close)
-
- i := 0
- var ent mountinfo.Entry
- for m.Next() {
- m.Copy(&ent)
-
- if i == len(tc.want) {
- t.Errorf("Next: got more than %d entries", i)
- t.FailNow()
- }
- if !ent.EqualWithIgnore(tc.want[i], "\x00") {
- t.Errorf("Next: entry %d\n got: %#v\nwant: %#v", i,
- ent, &tc.want[i])
- t.FailNow()
- } else {
- t.Logf("%s", &ent)
- }
-
- i++
- }
-
- if err = m.Err(); err != nil {
- t.Fatalf("Err: %v", err)
- }
- })
-
- if err := os.Remove(name); err != nil {
- t.Fatalf("cannot remove %q: %v", name, err)
- }
- }
-}
-
-func e(
- id, parent int,
- root, target, vfsOptstr string,
- fsType, source, fsOptstr string,
-) *mountinfo.Entry {
- return &mountinfo.Entry{
- ID: id,
- Parent: parent,
- Root: root,
- Target: target,
- VfsOptstr: vfsOptstr,
- FsType: fsType,
- Source: source,
- FsOptstr: fsOptstr,
- }
-}
diff --git a/test/internal/testsuite/fs.go b/test/internal/testsuite/fs.go
deleted file mode 100644
index 9acd24b6..00000000
--- a/test/internal/testsuite/fs.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package testsuite
-
-import (
- "errors"
- "fmt"
- "io/fs"
- "path/filepath"
- "strings"
-)
-
-var (
- // ErrFSBadLength is returned by [FS.Compare] for a directory with an
- // unexpected amount of dents.
- ErrFSBadLength = errors.New("bad dir length")
- // ErrFSBadData is returned by [FS.Compare] for a file with unexpected
- // contents.
- ErrFSBadData = errors.New("data differs")
- // ErrFSBadMode is returned by [FS.Compare] for an entry with unexpected
- // mode.
- ErrFSBadMode = errors.New("mode differs")
- // ErrFSInvalidEnt is returned by [FS.Compare] if an invalid [FS] is visited.
- ErrFSInvalidEnt = errors.New("invalid entry condition")
-)
-
-// FS represents part of a filesystem hierarchy.
-type FS struct {
- // Expected mode of corresponding entry.
- Mode fs.FileMode `json:"mode"`
- // Expected directory contents. The directory is not descended if Dir is nil.
- Dir map[string]*FS `json:"dir"`
- // Expected file contents. The file is not read if Data is nil.
- Data *string `json:"data"`
-}
-
-// dprintf calls printf if it is non-nil.
-func dprintf(printf func(format string, a ...any), format string, a ...any) {
- if printf == nil {
- return
- }
- printf(format, a...)
-}
-
-// printDir prints a failed [FS.Compare] directory.
-func printDir(
- printf func(format string, a ...any),
- prefix string,
- dir []fs.DirEntry,
-) {
- names := make([]string, len(dir))
- for i, ent := range dir {
- name := ent.Name()
- if ent.IsDir() {
- name += "/"
- }
- names[i] = fmt.Sprintf("%q", name)
- }
- dprintf(printf, "[FAIL] d %s: %s", prefix, strings.Join(names, " "))
-}
-
-// Compare compares the contents of prefix against the hierarchy described by s.
-func (s *FS) Compare(
- printf func(format string, a ...any),
- prefix string,
- e fs.FS,
-) error {
- if s.Data != nil {
- if s.Dir != nil {
- panic("invalid state")
- }
- panic("invalid compare call")
- }
-
- if s.Dir == nil {
- dprintf(printf, "[ OK ] s %s", prefix)
- return nil
- }
-
- var dir []fs.DirEntry
- if d, err := fs.ReadDir(e, prefix); err != nil {
- return err
- } else if len(d) != len(s.Dir) {
- printDir(printf, prefix, d)
- return ErrFSBadLength
- } else {
- dir = d
- }
-
- for _, got := range dir {
- name := got.Name()
-
- if want, ok := s.Dir[name]; !ok {
- printDir(printf, prefix, dir)
- return fs.ErrNotExist
- } else if want.Dir != nil && !got.IsDir() {
- printDir(printf, prefix, dir)
- return ErrFSInvalidEnt
- } else {
- name = filepath.Join(prefix, name)
-
- if fi, err := got.Info(); err != nil {
- return err
- } else if fi.Mode() != want.Mode {
- dprintf(printf, "[FAIL] m %s: %#o, want %#o",
- name, uint32(fi.Mode()), uint32(want.Mode))
- return ErrFSBadMode
- }
-
- if want.Data != nil {
- if want.Dir != nil {
- panic("invalid state")
- }
- if v, err := fs.ReadFile(e, name); err != nil {
- return err
- } else if string(v) != *want.Data {
- dprintf(printf,
- "[FAIL] f %s\n\t got: %s\n\twant: %s",
- name, v, *want.Data,
- )
- return ErrFSBadData
- }
- dprintf(printf, "[ OK ] f %s", name)
- } else if err := want.Compare(printf, name, e); err != nil {
- return err
- }
- }
- }
- dprintf(printf, "[ OK ] d %s", prefix)
- return nil
-}
diff --git a/test/internal/testsuite/fs_test.go b/test/internal/testsuite/fs_test.go
deleted file mode 100644
index 5c93bb46..00000000
--- a/test/internal/testsuite/fs_test.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package testsuite_test
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io/fs"
- "testing"
- "testing/fstest"
-
- "hakurei.app/test/internal/testsuite"
-)
-
-func TestCompare(t *testing.T) {
- var (
- fsPasswdSample = "u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh"
- fsGroupSample = "hakurei:x:65534:"
- )
-
- testCases := []struct {
- name string
-
- sample fstest.MapFS
- want *testsuite.FS
- wantOut string
- wantErr error
- }{
- {"skip", fstest.MapFS{}, &testsuite.FS{}, "[ OK ] s .\x00", nil},
- {"simple pass", fstest.MapFS{".hakurei": {Mode: 0x800001ed}},
- &testsuite.FS{Dir: map[string]*testsuite.FS{".hakurei": {Mode: 0x800001ed}}},
- "[ OK ] s .hakurei\x00[ OK ] d .\x00", nil},
- {"bad length", fstest.MapFS{".hakurei": {Mode: 0x800001ed}},
- &testsuite.FS{Dir: make(map[string]*testsuite.FS)},
- "[FAIL] d .: \".hakurei/\"\x00", testsuite.ErrFSBadLength},
- {"top level bad mode", fstest.MapFS{".hakurei": {Mode: 0x800001ed}},
- &testsuite.FS{Dir: map[string]*testsuite.FS{".hakurei": {Mode: 0xdeadbeef}}},
- "[FAIL] m .hakurei: 020000000755, want 033653337357\x00", testsuite.ErrFSBadMode},
- {"invalid entry condition", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}},
- &testsuite.FS{Dir: map[string]*testsuite.FS{"test": {Dir: make(map[string]*testsuite.FS)}}},
- "[FAIL] d .: \"test\"\x00", testsuite.ErrFSInvalidEnt},
- {"nonexistent", fstest.MapFS{"test": {Data: []byte{'0'}, Mode: 0644}},
- &testsuite.FS{Dir: map[string]*testsuite.FS{".test": {}}},
- "[FAIL] d .: \"test\"\x00", fs.ErrNotExist},
- {"file", fstest.MapFS{"etc": {Mode: 0x800001c0},
- "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644},
- "etc/group": {Data: []byte(fsGroupSample), Mode: 0644},
- }, &testsuite.FS{Dir: map[string]*testsuite.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*testsuite.FS{
- "passwd": {Mode: 0x1a4, Data: &fsPasswdSample},
- "group": {Mode: 0x1a4, Data: &fsGroupSample},
- }}}}, "[ OK ] f etc/group\x00[ OK ] f etc/passwd\x00[ OK ] d etc\x00[ OK ] d .\x00", nil},
- {"file differ", fstest.MapFS{"etc": {Mode: 0x800001c0},
- "etc/passwd": {Data: []byte(fsPasswdSample), Mode: 0644},
- "etc/group": {Data: []byte(fsGroupSample), Mode: 0644},
- }, &testsuite.FS{Dir: map[string]*testsuite.FS{"etc": {Mode: 0x800001c0, Dir: map[string]*testsuite.FS{
- "passwd": {Mode: 0x1a4, Data: &fsGroupSample},
- "group": {Mode: 0x1a4, Data: &fsGroupSample},
- }}}}, "[ OK ] f etc/group\x00[FAIL] f etc/passwd\n\t got: u0_a20:x:65534:65534:Hakurei:/var/lib/persist/module/hakurei/u0/a20:/run/current-system/sw/bin/zsh\n\twant: hakurei:x:65534:\x00", testsuite.ErrFSBadData},
- }
-
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- var buf bytes.Buffer
-
- err := tc.want.Compare(
- func(format string, a ...any) {
- _, _ = fmt.Fprintf(&buf, format+"\x00", a...)
- },
- ".", tc.sample,
- )
- if !errors.Is(err, tc.wantErr) {
- t.Errorf(
- "Compare: error = %v; wantErr %v",
- err, tc.wantErr,
- )
- }
-
- if buf.String() != tc.wantOut {
- t.Errorf(
- "Compare: output %q; want %q",
- &buf, tc.wantOut,
- )
- }
- })
- }
-}
diff --git a/test/internal/testsuite/proc.go b/test/internal/testsuite/proc.go
deleted file mode 100644
index e7ef1aed..00000000
--- a/test/internal/testsuite/proc.go
+++ /dev/null
@@ -1,353 +0,0 @@
-package testsuite
-
-import (
- "bytes"
- "errors"
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "strings"
- "syscall"
- "unsafe"
-
- "hakurei.app/fhs"
-)
-
-// Stat represents status information read from /proc/pid/stat.
-type Stat struct {
- // The process ID.
- PID int
- // The filename of the executable, with parenthesis stripped.
- Comm string
- // One of the following characters, indicating process state:
- //
- // R Running
- //
- // S Sleeping in an interruptible wait
- //
- // D Waiting in uninterruptible disk sleep
- //
- // Z Zombie
- //
- // T Stopped (on a signal) or (before Linux
- // 2.6.33) trace stopped
- //
- // t Tracing stop (Linux 2.6.33 onward)
- //
- // W Paging (only before Linux 2.6.0)
- //
- // X Dead (from Linux 2.6.0 onward)
- //
- // x Dead (Linux 2.6.33 to 3.13 only)
- //
- // K Wakekill (Linux 2.6.33 to 3.13 only)
- //
- // W Waking (Linux 2.6.33 to 3.13 only)
- //
- // P Parked (Linux 3.9 to 3.13 only)
- //
- // I Idle (Linux 4.14 onward)
- State byte
- // The process ID of the parent of this process.
- PPID int
- // The process group ID of the process.
- PGRP int
- // The session ID of the process.
- Session int
- // The controlling terminal of the process.
- TTYNR int
- // The ID of the foreground process group of the controlling terminal of the
- // process.
- TPGID int
- // The kernel flags word of the process. For bit meanings, see the PF_*
- // defines in the Linux kernel source file include/linux/sched.h.
- Flags uint
- // The number of minor faults the process has made which have not required
- // loading a memory page from disk.
- MinFlt uint
- // The number of minor faults that the process's waited-for children have
- // made.
- CMinFlt uint
- // The number of major faults the process has made which have required
- // loading a memory page from disk.
- MajFlt uint
- // The number of major faults that the process's waited-for children have
- // made.
- CMajFlt uint
- // Amount of time that this process has been scheduled in user mode,
- // measured in clock ticks.
- UTime uint
- // Amount of time that this process has been scheduled in kernel mode,
- // measured in clock ticks.
- STime uint
- // Amount of time that this process's waited-for children have been
- // scheduled in user mode, measured in clock ticks.
- CUTime int
- // Amount of time that this process's waited-for children have been
- // scheduled in kernel mode, measured in clock ticks.
- CSTime int
- // For processes running a real-time scheduling policy, this is the negated
- // scheduling priority, minus one.
- Priority int
- // The nice value, a value in the range 19 (low priority) to -20 (high
- // priority).
- Nice int
- // Number of threads in this process.
- NumThreads int
-
- // unmaintained field: itrealvalue
-
- // The time the process started after system boot. Since Linux 2.6, the
- // value is expressed in clock ticks.
- StartTime uint64
- // Virtual memory size in bytes.
- VSize uint
- // Resident set size in pages.
- RSS int
- // Soft limit in bytes on the rss of the process.
- RSSLim uint64
- // The address above which program text can run.
- StartCode uint64
- // The address below which program text can run.
- EndCode uint64
- // The address of the start (i.e., bottom) of the stack.
- StartStack uint64
- // The current value of ESP (stack pointer), as found in the kernel stack
- // page for the process.
- KSTKESP uint64
- // The current EIP (instruction pointer).
- KSTKEIP uint64
-
- // obsolete fields: signal, blocked, sigignore, sigcatch
-
- // This is the "channel" in which the process is waiting. It is the address
- // of a location in the kernel where the process is sleeping.
- WChan uint64
-
- // unmaintained fields: nswap, cnswap
-
- // Signal to be sent to parent when we die.
- ExitSignal int
- // CPU number last executed on.
- Processor int
- // Real-time scheduling priority, a number in the range 1 to 99 for processes
- // scheduled under a real-time policy, or 0, for non-real-time processes.
- RTPriority uint
- // Scheduling policy (see sched_setscheduler(2)). Decode using the SCHED_*
- // constants in linux/sched.h.
- Policy uint
- // Aggregated block I/O delays, measured in clock ticks (centiseconds).
- DelayAcctBlkIOTicks uint64
- // Guest time of the process (time spent running a virtual CPU for a guest
- // operating system), measured in clock ticks.
- GuestTime int
- // Guest time of the process's children, measured in clock ticks.
- CGuestTime int
-}
-
-// Executable is like [os.Executable], but for the process referred to by s.
-func (s *Stat) Executable() (string, error) {
- path, err := os.Readlink(filepath.Join(fhs.Proc, strconv.Itoa(s.PID), "exe"))
-
- // When the executable has been deleted then Readlink returns a
- // path appended with " (deleted)".
- return strings.TrimSuffix(path, " (deleted)"), err
-}
-
-// Stat populates stat with the proc filesystem entry referred to by s.
-func (s *Stat) Stat(stat *syscall.Stat_t) (err error) {
- err = syscall.Stat(filepath.Join(fhs.Proc, strconv.Itoa(s.PID)), stat)
- if err != nil {
- err = os.NewSyscallError("stat", err)
- }
- return
-}
-
-// Args reads arguments of the process referred to by s.
-func (s *Stat) Args() ([]string, error) {
- p, err := os.ReadFile(filepath.Join(fhs.Proc, strconv.Itoa(s.PID), "cmdline"))
- if err != nil {
- return nil, err
- }
- a := bytes.Split(p, []byte{0})
- if len(a) > 0 && len(a[len(a)-1]) == 0 {
- a = a[:len(a)-1]
- }
-
- args := make([]string, len(a))
- for i, arg := range a {
- args[i] = unsafe.String(unsafe.SliceData(arg), len(arg))
- }
- return args, nil
-}
-
-// ErrBadDelimiters is returned by [Stat.UnmarshalText] if one or both bytes of
-// the comm delimiter pair were missing or misplaced.
-var ErrBadDelimiters = errors.New("missing comm delimiters")
-
-// UnmarshalText populates the structure pointed to by s from text.
-func (s *Stat) UnmarshalText(text []byte) (err error) {
- var (
- discard uint64
- _uint64 = &discard
- _int64 = (*int64)(unsafe.Pointer(&discard))
-
- ld = bytes.Index(text, []byte("("))
- rd = bytes.LastIndex(text, []byte(")"))
- )
-
- if ld <= 0 || rd < 0 {
- return ErrBadDelimiters
- }
-
- if s.PID, err = strconv.Atoi(
- unsafe.String(unsafe.SliceData(text), ld-1),
- ); err != nil {
- return
- }
-
- s.Comm = string(text[ld+1 : rd])
-
- var (
- n int
-
- state string
- )
- n, err = fmt.Fscan(
- bytes.NewBuffer(text[rd+2:]),
- &state,
- &s.PPID,
- &s.PGRP,
- &s.Session,
- &s.TTYNR,
- &s.TPGID,
- &s.Flags,
- &s.MinFlt,
- &s.CMinFlt,
- &s.MajFlt,
- &s.CMajFlt,
- &s.UTime,
- &s.STime,
- &s.CUTime,
- &s.CSTime,
- &s.Priority,
- &s.Nice,
- &s.NumThreads,
- _int64,
- &s.StartTime,
- &s.VSize,
- &s.RSS,
- &s.RSSLim,
- &s.StartCode,
- &s.EndCode,
- &s.StartStack,
- &s.KSTKESP,
- &s.KSTKEIP,
- _uint64,
- _uint64,
- _uint64,
- _uint64,
- &s.WChan,
- _uint64,
- _uint64,
- &s.ExitSignal,
- &s.Processor,
- &s.RTPriority,
- &s.Policy,
- &s.DelayAcctBlkIOTicks,
- &s.GuestTime,
- &s.CGuestTime,
- )
- if err != nil {
- err = fmt.Errorf("field %d: %w", n, err)
- } else if len(state) != 1 {
- err = fmt.Errorf("invalid state %q", state)
- } else {
- s.State = state[0]
- }
- return
-}
-
-// A StatScanner continuously scans the proc filesystem for process status
-// information in /proc/pid/stat.
-type StatScanner struct {
- // Current entry.
- stat Stat
- // Cached top-level /proc entries.
- dents []os.DirEntry
- // Current progress through dents.
- i int
- // Whether the previous call to Scan had repopulated dents.
- wrapped bool
- // First stored error: a non-nil err disables the scanner.
- err error
-}
-
-// IsNotExist returns whether an error is [os.ErrNotExist] or ESRCH.
-func IsNotExist(err error) bool {
- return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ESRCH)
-}
-
-// Scan reads a process status information entry. It returns false if an
-// unrecoverable error is encountered, after which Scan no longer scans new
-// entries.
-func (s *StatScanner) Scan() bool {
- if s.err != nil {
- return false
- }
-
- if s.wrapped = s.i == len(s.dents); s.wrapped {
- if s.dents, s.err = os.ReadDir(fhs.Proc); s.err != nil {
- return false
- }
- s.i = 0
- if len(s.dents) == 0 {
- s.err = syscall.ENOTRECOVERABLE
- return false
- }
- }
-
- for s.i < len(s.dents) {
- dent := s.dents[s.i]
- s.i++
- if !dent.IsDir() {
- continue
- }
-
- pid, err := strconv.Atoi(dent.Name())
- if err != nil {
- continue
- }
-
- var p []byte
- p, err = os.ReadFile(filepath.Join(fhs.Proc, dent.Name(), "stat"))
- if err != nil {
- if IsNotExist(err) {
- continue
- }
- s.err = err
- return false
- }
-
- s.err = s.stat.UnmarshalText(p)
- if s.err == nil && pid != s.stat.PID {
- s.err = fmt.Errorf(
- "bad status information: dent=%d, stat=%d",
- pid, s.stat.PID,
- )
- }
- return s.err == nil
- }
- return s.Scan()
-}
-
-// Stat returns the address of the [Stat] structure populated by the last call
-// to Scan.
-func (s *StatScanner) Stat() *Stat { return &s.stat }
-
-// Err returns the stored error value.
-func (s *StatScanner) Err() error { return s.err }
-
-// Repopulated returns whether the last Scan call had re-read the proc filesystem.
-func (s *StatScanner) Repopulated() bool { return s.wrapped }
diff --git a/test/internal/testsuite/proc_test.go b/test/internal/testsuite/proc_test.go
deleted file mode 100644
index a8698e73..00000000
--- a/test/internal/testsuite/proc_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package testsuite_test
-
-import (
- "testing"
-
- "hakurei.app/test/internal/testsuite"
-)
-
-func BenchmarkStatScanner(b *testing.B) {
- var s testsuite.StatScanner
-
- for b.Loop() {
- if !s.Scan() {
- b.Fatal(s.Err())
- }
- }
-}
-
-func BenchmarkStatScannerFull(b *testing.B) {
- var s testsuite.StatScanner
-
- for b.Loop() {
- for s.Scan() {
- if s.Repopulated() {
- break
- }
- }
-
- if err := s.Err(); err != nil {
- b.Fatal(err)
- }
- }
-}
diff --git a/test/internal/testsuite/ptrace.go b/test/internal/testsuite/ptrace.go
deleted file mode 100644
index ccf0900c..00000000
--- a/test/internal/testsuite/ptrace.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package testsuite
-
-import (
- "crypto/sha512"
- "encoding/base64"
- "errors"
- "fmt"
- "os"
- "syscall"
- "unsafe"
-)
-
-const (
- // _PTRACE_ATTACH attaches to the process specified in pid.
- _PTRACE_ATTACH = 16
- // _PTRACE_DETACH restarts the stopped tracee as for PTRACE_CONT, but first
- // detaches from it.
- _PTRACE_DETACH = 17
-
- // _PTRACE_SECCOMP_GET_FILTER allows the tracer to dump the tracee's classic
- // BPF filters.
- _PTRACE_SECCOMP_GET_FILTER = 0x420c
-)
-
-// ptrace wraps the ptrace syscall.
-func ptrace(
- op uintptr,
- pid, addr int,
- data unsafe.Pointer,
-) (r uintptr, errno syscall.Errno) {
- r, _, errno = syscall.Syscall6(
- syscall.SYS_PTRACE,
- op,
- uintptr(pid),
- uintptr(addr),
- uintptr(data),
- 0, 0,
- )
- return
-}
-
-// ptraceAttach attaches to the process referred to by pid.
-func ptraceAttach(pid int) error {
- if _, errno := ptrace(_PTRACE_ATTACH, pid, 0, nil); errno != 0 {
- return os.NewSyscallError("PTRACE_ATTACH", errno)
- }
-
- var status syscall.WaitStatus
- for {
- if _, err := syscall.Wait4(
- pid,
- &status,
- syscall.WALL,
- nil,
- ); err != nil {
- if errors.Is(err, syscall.EINTR) {
- continue
- }
- return os.NewSyscallError("wait4", err)
- }
- switch {
- case status.Stopped():
- return nil
-
- case status.Continued():
- continue
-
- case status.Signaled():
- return fmt.Errorf(
- "tracee terminated by signal %s",
- status.Signal(),
- )
-
- case status.Exited():
- return fmt.Errorf(
- "tracee terminated unexpectedly with code %d",
- status.ExitStatus(),
- )
- }
- }
-}
-
-// ptraceDetach detaches from the attached process referred to by pid.
-func ptraceDetach(pid int) error {
- if _, errno := ptrace(_PTRACE_DETACH, pid, 0, nil); errno != 0 {
- return os.NewSyscallError("PTRACE_DETACH", errno)
- }
- return nil
-}
-
-// getFilter dumps the specified tracee's cBPF filter at the specified index
-// and returns the resulting payload. T must be eight bytes long and must not
-// contain pointers.
-func getFilter(pid, index int) ([]syscall.SockFilter, error) {
- var buf []syscall.SockFilter
- if n, errno := ptrace(
- _PTRACE_SECCOMP_GET_FILTER,
- pid, index, nil,
- ); errno != 0 {
- return nil, os.NewSyscallError("PTRACE_SECCOMP_GET_FILTER", errno)
- } else {
- buf = make([]syscall.SockFilter, n)
- }
- if _, errno := ptrace(
- _PTRACE_SECCOMP_GET_FILTER,
- pid, index, unsafe.Pointer(&buf[0]),
- ); errno != 0 {
- return nil, os.NewSyscallError("PTRACE_SECCOMP_GET_FILTER", errno)
- }
- return buf, nil
-}
-
-// CheckFilter checks the process at pid to have its first filter's contents
-// match the specified sha512 checksum.
-func CheckFilter(pid, index int, sum [sha512.Size]byte) (err error) {
- if err = ptraceAttach(pid); err != nil {
- return
- }
- defer func() {
- if detachErr := ptraceDetach(pid); err == nil {
- err = detachErr
- }
- }()
-
- var buf []syscall.SockFilter
- h := sha512.New()
- if buf, err = getFilter(pid, index); err != nil {
- return
- } else {
- h.Write(unsafe.Slice(
- (*byte)(unsafe.Pointer(&buf[0])),
- uintptr(len(buf))*unsafe.Sizeof(buf[0]),
- ))
- }
-
- if got := h.Sum(nil); string(got) != string(sum[:]) {
- return fmt.Errorf(
- "bad filter\n\t got: %s\n\twant: %s",
- base64.StdEncoding.EncodeToString(got),
- base64.StdEncoding.EncodeToString(sum[:]),
- )
- }
- return
-}
diff --git a/test/internal/testsuite/ptrace_test.go b/test/internal/testsuite/ptrace_test.go
deleted file mode 100644
index eaaed131..00000000
--- a/test/internal/testsuite/ptrace_test.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package testsuite
-
-import (
- "syscall"
- "testing"
- "unsafe"
-)
-
-func TestBlockSize(t *testing.T) {
- if sz := unsafe.Sizeof(syscall.SockFilter{}); sz != 8 {
- t.Fatalf("invalid filter block size %d", sz)
- }
-}
diff --git a/test/internal/testsuite/testsuite.go b/test/internal/testsuite/testsuite.go
deleted file mode 100644
index 456c36b4..00000000
--- a/test/internal/testsuite/testsuite.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// Package testsuite provides many quick-and-dirty integration testing utilities.
-//
-// Attempting to import this package outside testing causes the resulting
-// program to panic.
-package testsuite
-
-import (
- "bufio"
- "context"
- "crypto/sha512"
- "errors"
- "log"
- "os"
- "os/exec"
- "os/signal"
- "sync"
- "syscall"
- "time"
-)
-
-// ReceiveSignals blocks until a termination signal arrives, and terminates.
-func ReceiveSignals() {
- s := make(chan os.Signal, 3)
- signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
- log.Fatalf("terminating on signal %s", <-s)
-}
-
-// MustRun runs command and terminates the testsuite on error.
-func MustRun(cred *syscall.Credential, extraEnv []string, command ...string) {
- cmd := exec.Command(command[0], command[1:]...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGKILL,
- Credential: cred,
- }
- if len(extraEnv) != 0 {
- cmd.Env = append(cmd.Environ(), extraEnv...)
- }
- if err := cmd.Run(); err != nil {
- log.Fatal(err)
- }
-}
-
-// ErrUnexpectedSuccess is returned for processes expected to exit with a
-// non-zero code, but failed to do so.
-var ErrUnexpectedSuccess = errors.New("process unexpectedly exited with code 0")
-
-// MustFail runs command and terminates the testsuite if the program fails to
-// start or exits with code 0.
-func MustFail(cred *syscall.Credential, extraEnv []string, command ...string) {
- cmd := exec.Command(command[0], command[1:]...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGKILL,
- Credential: cred,
- }
- if len(extraEnv) != 0 {
- cmd.Env = append(cmd.Environ(), extraEnv...)
- }
- if err := cmd.Run(); err == nil {
- log.Fatal(ErrUnexpectedSuccess)
- } else if e, ok := errors.AsType[*exec.ExitError](err); !ok {
- log.Fatal(err)
- } else if !e.Exited() {
- log.Fatal(e)
- }
-}
-
-// MustStart starts cmd and returns a channel delivering its wait error.
-func MustStart(cmd *exec.Cmd) (done <-chan error) {
- if err := cmd.Start(); err != nil {
- log.Fatal(err)
- }
- d := make(chan error)
- go func() { d <- cmd.Wait() }()
- return d
-}
-
-// MustStartWith wraps [MustStart] and creates the [exec.Cmd] object internally.
-func MustStartWith(
- ctx context.Context,
- cred *syscall.Credential,
- extraEnv []string,
- files []*os.File,
- command ...string,
-) (proc *os.Process, done <-chan error) {
- cmd := exec.CommandContext(ctx, command[0], command[1:]...)
- cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
- cmd.ExtraFiles = files
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGTERM,
- Credential: cred,
- }
- if len(extraEnv) != 0 {
- cmd.Env = append(cmd.Environ(), extraEnv...)
- }
- return cmd.Process, MustStart(cmd)
-}
-
-// MustCheckFilter is like [CheckFilter], but terminates the test suite if a
-// non-nil error is returned. Otherwise, the tracee is terminated after it
-// resumes.
-func MustCheckFilter(pid int, sum [sha512.Size]byte) {
- // podman installs its own filter
- if err := CheckFilter(pid, 1, sum); err != nil {
- log.Fatal(err)
- } else if err = syscall.Kill(pid, syscall.SIGTERM); err != nil {
- log.Fatalf("cannot terminate tracee: %v", err)
- }
-}
-
-// FilterTerminated returns a non-nil error if err is not an [exec.ExitError]
-// describing a process terminated by a syscall.SIGTERM signal.
-func FilterTerminated(err error) error {
- if err == nil {
- return ErrUnexpectedSuccess
- }
-
- e, ok := errors.AsType[*exec.ExitError](err)
- if !ok {
- return err
- }
-
- if e.ExitCode() == 0x80+int(syscall.SIGTERM) {
- return nil
- }
- return e
-}
-
-// Poll repeatedly runs command until it succeeds.
-func Poll(
- d time.Duration,
- cred *syscall.Credential,
- extraEnv []string,
- command ...string,
-) {
- for range time.NewTicker(d).C {
- cmd := exec.Command(command[0], command[1:]...)
- cmd.SysProcAttr = &syscall.SysProcAttr{
- Pdeathsig: syscall.SIGKILL,
- Credential: cred,
- }
- if len(extraEnv) != 0 {
- cmd.Env = append(cmd.Environ(), extraEnv...)
- }
- if err := cmd.Run(); err != nil {
- if e, ok := errors.AsType[*exec.ExitError](err); ok && e.Exited() {
- continue
- }
- log.Fatal(err)
- }
- break
- }
-}
-
-const (
- // XDGRuntimeDir is the hardcoded XDG runtime directory for the user
- // described by [GetUser].
- XDGRuntimeDir = "/var/run/user/1000"
-
- // XDGRuntimeEnv is the environment variable string for XDG_RUNTIME_DIR.
- XDGRuntimeEnv = "XDG_RUNTIME_DIR=" + XDGRuntimeDir
-)
-
-// MustStartSessionBus starts a session bus that is never explicitly terminated.
-// The test suite is terminated if the session bus daemon terminates.
-func MustStartSessionBus(cred *syscall.Credential) (dbusEnv string) {
- r, w, err := os.Pipe()
- if err != nil {
- log.Fatal(err)
- }
-
- // this is never explicitly terminated
- _, done := MustStartWith(
- context.Background(), cred, nil, []*os.File{w},
- "dbus-daemon",
- "--print-address=3",
- "--address=unix:path="+XDGRuntimeDir+"/dbus",
- "--session",
- "--nofork",
- "--nopidfile",
- )
-
- go func() {
- if _err := <-done; _err != nil {
- log.Fatal(_err)
- }
- log.Fatal("session bus terminated unexpectedly")
- }()
-
- dbusEnv, err = bufio.NewReader(r).ReadString('\n')
- if err != nil {
- log.Fatal(err)
- }
- dbusEnv = dbusEnv[:len(dbusEnv)-1]
- log.Printf("dbus listening on %s", dbusEnv)
- dbusEnv = "DBUS_SESSION_BUS_ADDRESS=" + dbusEnv
-
- if err = r.Close(); err != nil {
- log.Fatal(err)
- }
- return
-}
-
-const (
- // SwayEnv is the environment variable string for the sway IPC socket.
- SwayEnv = "SWAYSOCK=" + XDGRuntimeDir + "/sway"
- // WaylandEnv is the environment variable string for the wayland display.
- WaylandEnv = "WAYLAND_DISPLAY=wayland-1"
-)
-
-// MustStartSway starts the sway wayland display server which must be terminated
-// by calling [TerminateSway].
-func MustStartSway(
- wg *sync.WaitGroup,
- cred *syscall.Credential,
- dbusEnv string,
-) {
- wg.Go(func() {
- // this is terminated via swaymsg
- _, done := MustStartWith(
- context.Background(), cred, []string{
- "WLR_BACKENDS=headless",
- XDGRuntimeEnv,
- SwayEnv,
- dbusEnv,
- }, nil,
- "sway",
- )
- if err := <-done; err != nil {
- log.Fatal(err)
- }
- })
-
- Poll(
- 50*time.Millisecond,
- cred,
- []string{SwayEnv},
- "swaymsg",
- )
- log.Printf("sway available via %s", SwayEnv)
-}
-
-// TerminateSway requests for the sway server to terminate via sway IPC.
-func TerminateSway(cred *syscall.Credential) {
- MustFail(cred, []string{SwayEnv}, "swaymsg", "exit")
-}
-
-// MustStartPipeWire starts a PipeWire server that is never explicitly
-// terminated. The test suite is terminated if the PipeWire server terminates.
-func MustStartPipeWire(cred *syscall.Credential, dbusEnv string) {
- // this is never explicitly terminated
- _, done := MustStartWith(
- context.Background(), cred, []string{
- XDGRuntimeEnv,
- dbusEnv,
- }, nil,
- "pipewire",
- )
-
- go func() {
- if _err := <-done; _err != nil {
- log.Fatal(_err)
- }
- log.Fatal("pipewire terminated unexpectedly")
- }()
-
- Poll(50*time.Millisecond, cred, []string{
- XDGRuntimeEnv,
- dbusEnv,
- },
- "wpctl",
- "status",
- )
-
- _, _done := MustStartWith(
- context.Background(), cred, []string{
- XDGRuntimeEnv,
- dbusEnv,
- }, nil,
- "wireplumber",
- )
-
- go func() {
- if _err := <-_done; _err != nil {
- log.Fatal(_err)
- }
- log.Fatal("wireplumber terminated unexpectedly")
- }()
-}
diff --git a/test/internal/testsuite/testsuite_guard.go b/test/internal/testsuite/testsuite_guard.go
deleted file mode 100644
index 0859d17f..00000000
--- a/test/internal/testsuite/testsuite_guard.go
+++ /dev/null
@@ -1,15 +0,0 @@
-//go:build !testsuite && !tester
-
-package testsuite
-
-import (
- "os"
- "testing"
-)
-
-func init() {
- if !testing.Testing() {
- println("package testsuite imported in non-testsuite program")
- os.Exit(1)
- }
-}
diff --git a/test/internal/testsuite/testsuite_root.go b/test/internal/testsuite/testsuite_root.go
deleted file mode 100644
index 11f503ef..00000000
--- a/test/internal/testsuite/testsuite_root.go
+++ /dev/null
@@ -1,17 +0,0 @@
-//go:build testsuite
-
-package testsuite
-
-import (
- "log"
- "os"
-)
-
-func init() {
- if os.Geteuid() != 0 {
- log.Fatal("this program must run as root")
- }
-
- log.SetFlags(0)
- log.SetPrefix("testsuite: ")
-}