mesh/merge-gate waiting in the build queue since 01:13
mesh/delivery superseded: a newer head of the same pull request
mesh/check failed: unit: diskload/cmd/disk-load: # diskload/cmd/disk-load [diskload/cmd/disk-load.test] | 4ef2fa2dff1c on 3bbae41037e3 | unit 4096 run…
171 lines
5.6 KiB
Go
171 lines
5.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
const mb = 1 << 20
|
|
|
|
// megabytes writes a file of n MiB under the fake root.
|
|
func (f *fake) megabytes(path string, n int) { f.file(path, strings.Repeat("x", n*mb)) }
|
|
|
|
func aFullDisk(t *testing.T) *fake {
|
|
f := newFake(t)
|
|
f.megabytes("var/lib/docker/overlay2/a/layer", 8)
|
|
f.megabytes("var/lib/docker/overlay2/b/layer", 4)
|
|
f.megabytes("var/log/big.log", 3)
|
|
f.megabytes("home/jo/video.mkv", 6)
|
|
f.megabytes("home/jo/deep/er/est/file", 1)
|
|
f.dir("empty")
|
|
return f
|
|
}
|
|
|
|
func folders(t *testing.T, m *Machine, args map[string]any) FolderReport {
|
|
t.Helper()
|
|
out, err := tool(t, m, "disk_folders").Run(args)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return out.(FolderReport)
|
|
}
|
|
|
|
func paths(r FolderReport) string {
|
|
var p []string
|
|
for _, f := range r.Largest {
|
|
p = append(p, f.Path)
|
|
}
|
|
return strings.Join(p, ",")
|
|
}
|
|
|
|
// 1804/AC1: given a disk with folders of known sizes, when disk_folders runs with the defaults, then it answers
|
|
// the largest folders to depth 2, biggest first, with bytes and GiB, and the total of the path.
|
|
func TestFoldersAreRankedBiggestFirstToDepthTwoByDefault(t *testing.T) {
|
|
r := folders(t, aFullDisk(t).machine(), nil)
|
|
if got := paths(r); !strings.HasPrefix(got, "/var,/var/lib,") || !strings.Contains(got, "/home,/home/jo") {
|
|
t.Fatalf("ranked %s", got)
|
|
}
|
|
if r.Path != "/" || r.Partial || r.Unreadable != 0 {
|
|
t.Errorf("%+v", r)
|
|
}
|
|
top := r.Largest[0]
|
|
if top.Path != "/var" || top.Depth != 1 || top.Bytes < 15*mb || top.Bytes > 16*mb || top.GiB <= 0 {
|
|
t.Errorf("top %+v", top)
|
|
}
|
|
for _, f := range r.Largest {
|
|
if f.Depth > 2 || f.Depth < 1 {
|
|
t.Errorf("%+v is outside depth 2", f)
|
|
}
|
|
if strings.Contains(f.Path, "overlay2") {
|
|
t.Errorf("%s is deeper than 2", f.Path)
|
|
}
|
|
}
|
|
if r.Bytes < 22*mb || r.Bytes > 24*mb {
|
|
t.Errorf("total %d", r.Bytes)
|
|
}
|
|
}
|
|
|
|
// 1804/AC2: given a path, a depth and a limit, then only that folder is measured, depth is held at 3 and limit at 100.
|
|
func TestFoldersTakeAPathADepthAndALimitHeldAtThreeAndAHundred(t *testing.T) {
|
|
f := aFullDisk(t)
|
|
r := folders(t, f.machine(), map[string]any{"path": "/var/lib/docker", "depth": 1.0})
|
|
if r.Path != "/var/lib/docker" || paths(r) != "/var/lib/docker/overlay2" {
|
|
t.Errorf("%s: %s", r.Path, paths(r))
|
|
}
|
|
r = folders(t, f.machine(), map[string]any{"path": "/home", "depth": 9.0, "limit": 2.0})
|
|
if len(r.Largest) != 2 {
|
|
t.Errorf("limit 2 gave %d", len(r.Largest))
|
|
}
|
|
r = folders(t, f.machine(), map[string]any{"path": "/home", "depth": 9.0})
|
|
if got := paths(r); !strings.Contains(got, "/home/jo/deep/er") || strings.Contains(got, "/home/jo/deep/er/est") {
|
|
t.Errorf("depth 3 under /home is /home/jo/deep/er: %s", got)
|
|
}
|
|
tl := tool(t, f.machine(), "disk_folders")
|
|
for _, bad := range []map[string]any{{"depth": 0.0}, {"limit": 0.0}, {"depth": "deep"}, {"path": "relative/dir"}, {"path": "/nope"}, {"path": "/var/log/big.log"}} {
|
|
if _, err := tl.Run(bad); err == nil {
|
|
t.Errorf("%v was accepted", bad)
|
|
}
|
|
}
|
|
for i := 0; i < 120; i++ {
|
|
f.file(fmt.Sprintf("many/d%03d/f", i), "x")
|
|
}
|
|
if r := folders(t, f.machine(), map[string]any{"path": "/many", "limit": 500.0}); len(r.Largest) != 100 {
|
|
t.Errorf("limit held at %d, not 100", len(r.Largest))
|
|
}
|
|
}
|
|
|
|
// 1804/AC3: given a mount below the path, then it is never entered or counted, and is named.
|
|
func TestFoldersNeverCrossIntoAnotherFilesystem(t *testing.T) {
|
|
f := aFullDisk(t)
|
|
m := f.machine()
|
|
m.DevOf = func(path string, info fs.FileInfo) uint64 {
|
|
if path == "/var/lib/docker" {
|
|
return 7
|
|
}
|
|
return 1
|
|
}
|
|
r := folders(t, m, nil)
|
|
if strings.Contains(paths(r), "docker") || len(r.Skipped) != 1 || r.Skipped[0] != "/var/lib/docker" {
|
|
t.Errorf("%s, skipped %v", paths(r), r.Skipped)
|
|
}
|
|
if r.Bytes > 14*mb {
|
|
t.Errorf("total %d includes the mount", r.Bytes)
|
|
}
|
|
}
|
|
|
|
// 1804/AC4: given folders it cannot read, then they are counted and named and the rest is still answered.
|
|
func TestUnreadableFoldersAreCountedAndNamed(t *testing.T) {
|
|
f := aFullDisk(t)
|
|
m := f.machine()
|
|
real := os.ReadDir
|
|
m.ReadDir = func(p string) ([]os.DirEntry, error) {
|
|
if strings.HasSuffix(p, "/var/log") || strings.HasSuffix(p, "/overlay2/b") {
|
|
return nil, fmt.Errorf("open %s: permission denied", p)
|
|
}
|
|
return real(p)
|
|
}
|
|
r := folders(t, m, map[string]any{"depth": 3.0})
|
|
if r.Unreadable != 2 || strings.Join(r.UnreadableFolders, ",") != "/var/lib/docker/overlay2/b,/var/log" {
|
|
t.Errorf("%d: %v", r.Unreadable, r.UnreadableFolders)
|
|
}
|
|
if r.Partial || !strings.Contains(strings.Join(r.Said, "\n"), "2 folders could not be read") {
|
|
t.Errorf("%+v", r)
|
|
}
|
|
}
|
|
|
|
// 1804/AC5: given a walk that outlasts its bound, then it answers what it measured, partial, with where it stopped.
|
|
func TestALongWalkAnswersPartialAndSaysWhereItStopped(t *testing.T) {
|
|
f := aFullDisk(t)
|
|
m := f.machine()
|
|
now := time.Unix(0, 0)
|
|
m.Clock = func() time.Time { now = now.Add(10 * time.Second); return now }
|
|
r := folders(t, m, nil)
|
|
if !r.Partial || r.StoppedAt == "" || len(r.Said) < 2 || !strings.HasPrefix(r.Said[0], "PARTIAL") || !strings.Contains(r.Said[0], r.StoppedAt) {
|
|
t.Errorf("%+v", r)
|
|
}
|
|
}
|
|
|
|
// 1804/AC6: a hard link is counted once, and the tool changes nothing.
|
|
func TestAHardLinkIsCountedOnce(t *testing.T) {
|
|
f := newFake(t)
|
|
f.megabytes("a/big", 5)
|
|
if err := os.Link(f.root+"/a/big", f.root+"/a/again"); err != nil {
|
|
t.Skip(err)
|
|
}
|
|
r := folders(t, f.machine(), nil)
|
|
if r.Bytes > 6*mb {
|
|
t.Errorf("counted twice: %d", r.Bytes)
|
|
}
|
|
}
|
|
|
|
// 1804/AC7: the bound sits below the 30 s the tool runner gives a call, so a partial answer reaches its caller.
|
|
func TestTheWalkBoundIsBelowTheToolRunnersCallLimit(t *testing.T) {
|
|
if FolderBudget >= 30*time.Second {
|
|
t.Errorf("the budget %s is not below the 30 s a call gets", FolderBudget)
|
|
}
|
|
}
|