blob: 42b9df52d78014ec12b890d177c102c08123145a [file] [log] [blame]
Lorenz Brunee17d832022-10-18 12:02:45 +00001package gpt
2
3import (
4 "bytes"
5 "crypto/sha256"
6 "io"
7 "os"
8 "testing"
9
10 "github.com/google/uuid"
Lorenz Brunad131882023-06-28 16:42:20 +020011
12 "source.monogon.dev/metropolis/pkg/blockdev"
Lorenz Brunee17d832022-10-18 12:02:45 +000013)
14
Lorenz Brunee17d832022-10-18 12:02:45 +000015func TestFreeSpaces(t *testing.T) {
16 cases := []struct {
17 name string
18 parts []*Partition
19 expected [][2]int64
20 expectedOverlap bool
21 }{
22 {"Empty", []*Partition{}, [][2]int64{{34, 2015}}, false},
23 {"OnePart", []*Partition{
24 {Type: PartitionTypeEFISystem, FirstBlock: 200, LastBlock: 1499},
25 }, [][2]int64{
26 {34, 200},
27 {1500, 2015},
28 }, false},
29 {"TwoOverlappingParts", []*Partition{
30 {Type: PartitionTypeEFISystem, FirstBlock: 200, LastBlock: 1499},
31 {Type: PartitionTypeEFISystem, FirstBlock: 1000, LastBlock: 1999},
32 }, [][2]int64{
33 {34, 200},
34 {2000, 2015},
35 }, true},
36 {"Full", []*Partition{
37 {Type: PartitionTypeEFISystem, FirstBlock: 34, LastBlock: 999},
38 {Type: PartitionTypeEFISystem, FirstBlock: 1000, LastBlock: 2014},
39 }, [][2]int64{}, false},
40 {"TwoSpacedParts", []*Partition{
41 {Type: PartitionTypeEFISystem, FirstBlock: 500, LastBlock: 899},
42 {Type: PartitionTypeEFISystem, FirstBlock: 1200, LastBlock: 1799},
43 }, [][2]int64{
44 {34, 500},
45 {900, 1200},
46 {1800, 2015},
47 }, false},
48 }
49
50 // Partitions are created manually as AddPartition calls FreeSpaces itself,
51 // which makes the test unreliable as well as making failures very hard to
52 // debug.
53 for _, c := range cases {
54 t.Run(c.name, func(t *testing.T) {
Lorenz Brunad131882023-06-28 16:42:20 +020055 d := blockdev.MustNewMemory(512, 2048) // 1MiB
56 g, err := New(d)
57 if err != nil {
58 panic(err)
Lorenz Brunee17d832022-10-18 12:02:45 +000059 }
Lorenz Brunad131882023-06-28 16:42:20 +020060 g.Partitions = c.parts
Lorenz Brunee17d832022-10-18 12:02:45 +000061 fs, overlap, err := g.GetFreeSpaces()
62 if err != nil {
63 t.Fatal(err)
64 }
65 if overlap != c.expectedOverlap {
66 t.Errorf("expected overlap %v, got %v", c.expectedOverlap, overlap)
67 }
68 if len(fs) != len(c.expected) {
69 t.Fatalf("expected %v, got %v", c.expected, fs)
70 }
71 for i := range fs {
72 if fs[i] != c.expected[i] {
73 t.Errorf("free space mismatch at pos %d: got [%d, %d), expected [%d, %d)", i, fs[i][0], fs[i][1], c.expected[i][0], c.expected[i][1])
74 }
75 }
76 })
77 }
78}
79
80func TestRoundTrip(t *testing.T) {
81 if os.Getenv("IN_KTEST") == "true" {
82 t.Skip("In ktest")
83 }
Lorenz Brunad131882023-06-28 16:42:20 +020084 d := blockdev.MustNewMemory(512, 2048) // 1 MiB
85
Lorenz Brunee17d832022-10-18 12:02:45 +000086 g := Table{
Lorenz Brunad131882023-06-28 16:42:20 +020087 ID: uuid.NewSHA1(uuid.Nil, []byte("test")),
88 BootCode: []byte("just some test code"),
Lorenz Brunee17d832022-10-18 12:02:45 +000089 Partitions: []*Partition{
90 nil,
91 // This emoji is very complex and exercises UTF16 surrogate encoding
92 // and composing.
Lorenz Brunad131882023-06-28 16:42:20 +020093 {Name: "Test 🏃‍♂️", FirstBlock: 10, LastBlock: 19, Type: PartitionTypeEFISystem, ID: uuid.NewSHA1(uuid.Nil, []byte("test1")), Attributes: AttrNoBlockIOProto},
Lorenz Brunee17d832022-10-18 12:02:45 +000094 nil,
Lorenz Brunad131882023-06-28 16:42:20 +020095 {Name: "Test2", FirstBlock: 20, LastBlock: 49, Type: PartitionTypeEFISystem, ID: uuid.NewSHA1(uuid.Nil, []byte("test2")), Attributes: 0},
Lorenz Brunee17d832022-10-18 12:02:45 +000096 },
Lorenz Brunad131882023-06-28 16:42:20 +020097 b: d,
Lorenz Brunee17d832022-10-18 12:02:45 +000098 }
Lorenz Brunad131882023-06-28 16:42:20 +020099 if err := g.Write(); err != nil {
100 t.Fatalf("Error while writing Table: %v", err)
Lorenz Brunee17d832022-10-18 12:02:45 +0000101 }
Lorenz Brunad131882023-06-28 16:42:20 +0200102
Lorenz Brunee17d832022-10-18 12:02:45 +0000103 originalHash := sha256.New()
Lorenz Brunad131882023-06-28 16:42:20 +0200104 sr1 := io.NewSectionReader(d, 0, d.BlockSize()*d.BlockCount())
105 if _, err := io.CopyBuffer(originalHash, sr1, make([]byte, d.OptimalBlockSize())); err != nil {
Lorenz Brunee17d832022-10-18 12:02:45 +0000106 panic(err)
107 }
108
Lorenz Brunad131882023-06-28 16:42:20 +0200109 g2, err := Read(d)
Lorenz Brunee17d832022-10-18 12:02:45 +0000110 if err != nil {
111 t.Fatalf("Failed to read back GPT: %v", err)
112 }
113 if g2.ID != g.ID {
114 t.Errorf("Disk UUID changed when reading back: %v", err)
115 }
116 // Destroy primary GPT
Lorenz Brunad131882023-06-28 16:42:20 +0200117 d.Zero(1*d.BlockSize(), 5*d.BlockSize())
118 g3, err := Read(d)
Lorenz Brunee17d832022-10-18 12:02:45 +0000119 if err != nil {
120 t.Fatalf("Failed to read back GPT with primary GPT destroyed: %v", err)
121 }
122 if g3.ID != g.ID {
123 t.Errorf("Disk UUID changed when reading back: %v", err)
124 }
Lorenz Brunad131882023-06-28 16:42:20 +0200125 if err := g3.Write(); err != nil {
Lorenz Brunee17d832022-10-18 12:02:45 +0000126 t.Fatalf("Error while writing back GPT: %v", err)
127 }
Lorenz Brunee17d832022-10-18 12:02:45 +0000128 rewrittenHash := sha256.New()
Lorenz Brunad131882023-06-28 16:42:20 +0200129 sr2 := io.NewSectionReader(d, 0, d.BlockSize()*d.BlockCount())
130 if _, err := io.CopyBuffer(rewrittenHash, sr2, make([]byte, d.OptimalBlockSize())); err != nil {
Lorenz Brunee17d832022-10-18 12:02:45 +0000131 panic(err)
132 }
133 if !bytes.Equal(originalHash.Sum([]byte{}), rewrittenHash.Sum([]byte{})) {
134 t.Errorf("Write/Read/Write test was not reproducible: %x != %x", originalHash.Sum([]byte{}), rewrittenHash.Sum([]byte{}))
135 }
136}