package snapshot import ( "bytes" "testing" "github.com/andydixon/mapsize/internal/inventory" ) func sample() *inventory.Tree { t := inventory.New("/srv", inventory.KindDir) a := t.Add(0, "a", inventory.KindDir) b := t.Add(0, "b", inventory.KindDir) f1 := t.Add(a, "x.iso", inventory.KindFile) t.Node(f1).Size, t.Node(f1).Alloc = 1000, 1024 f2 := t.Add(b, "y.log", inventory.KindFile) t.Node(f2).Size, t.Node(f2).Alloc = 50, 4096 t.Node(f2).Flags = inventory.FlagSparse t.Recompute() t.Stats.Files, t.Stats.Dirs = 2, 3 t.Stats.AddError(inventory.ErrorRecord{Node: b, Kind: inventory.ErrPermission, Msg: "denied"}) return t } func TestRoundTrip(t *testing.T) { src := sample() var buf bytes.Buffer if err := Save(&buf, src); err != nil { t.Fatal(err) } got, err := Load(bytes.NewReader(buf.Bytes())) if err != nil { t.Fatal(err) } if got.Len() != src.Len() { t.Fatalf("len %d want %d", got.Len(), src.Len()) } for i := range src.Len() { a, b := src.Node(inventory.NodeID(i)), got.Node(inventory.NodeID(i)) if a.Name != b.Name || a.TotSize != b.TotSize || a.TotAlloc != b.TotAlloc || a.Flags != b.Flags || src.Path(inventory.NodeID(i)) != got.Path(inventory.NodeID(i)) || src.ExtName(a) != got.ExtName(b) { t.Fatalf("node %d differs: %+v vs %+v", i, a, b) } } if got.Stats.ErrCounts[inventory.ErrPermission] != 1 || len(got.Stats.Errors) != 1 { t.Fatalf("errors not restored: %+v", got.Stats) } } func TestCorruptionDetected(t *testing.T) { var buf bytes.Buffer Save(&buf, sample()) data := buf.Bytes() // Flip a byte in the checksum trailer. bad := append([]byte(nil), data...) bad[len(bad)-1] ^= 0xff if _, err := Load(bytes.NewReader(bad)); err == nil { t.Fatal("checksum corruption not detected") } // Every truncation must fail cleanly. for i := 0; i < len(data)-1; i += 7 { if _, err := Load(bytes.NewReader(data[:i])); err == nil { t.Fatalf("truncation at %d accepted", i) } } } func FuzzLoad(f *testing.F) { var buf bytes.Buffer Save(&buf, sample()) f.Add(buf.Bytes()) f.Add([]byte("MAPSIZE\x00\x01\x00\x01\x00")) f.Fuzz(func(t *testing.T, data []byte) { tr, err := Load(bytes.NewReader(data)) if err != nil { return } // A successfully loaded tree must be internally consistent. for i := 1; i < tr.Len(); i++ { if p := tr.Node(inventory.NodeID(i)).Parent; p >= inventory.NodeID(i) { t.Fatalf("parent %d >= child %d", p, i) } } _ = tr.Path(inventory.NodeID(tr.Len() - 1)) }) } func TestCompare(t *testing.T) { old := sample() nw := inventory.New("/srv", inventory.KindDir) a := nw.Add(0, "a", inventory.KindDir) f1 := nw.Add(a, "x.iso", inventory.KindFile) nw.Node(f1).Size, nw.Node(f1).Alloc = 5000, 5120 c := nw.Add(0, "c", inventory.KindDir) f3 := nw.Add(c, "z", inventory.KindFile) nw.Node(f3).Size, nw.Node(f3).Alloc = 10, 4096 nw.Recompute() d := Compare(old, nw) if d.StatusOf(f1, inventory.SizeAllocated) != Grew || d.Delta(f1, inventory.SizeAllocated) != 4096 { t.Fatalf("x.iso: %v %d", d.StatusOf(f1, 0), d.Delta(f1, 0)) } if d.StatusOf(c, 0) != Added { t.Fatal("c should be new") } if len(d.Removed) != 1 || old.Node(d.Removed[0]).Name != "b" { t.Fatalf("removed %v", d.Removed) } ch := d.Changes(10, inventory.SizeAllocated) paths := map[string]Status{} for _, c := range ch { paths[c.Path] = c.Status } if paths["/srv/a/x.iso"] != Grew || paths["/srv/b"] != Removed || paths["/srv/c"] != Added { t.Fatalf("changes %+v", ch) } if _, ok := paths["/srv/a"]; ok { t.Fatal("/srv/a should be explained by its child") } if _, ok := paths["/srv/c/z"]; ok { t.Fatal("contents of new dirs should not be listed separately") } }