blob: df2970bb2eec22bfa642e568a08bf8b58f2f5684 [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"
11)
12
13func TestUUIDTranspose(t *testing.T) {
14 testUUID := uuid.MustParse("00112233-4455-6677-c899-aabbccddeeff")
15 mixedEndianUUID := mangleUUID(testUUID)
16 expectedMixedEndianUUID := [16]byte{0x33, 0x22, 0x11, 0x00, 0x55, 0x44, 0x77, 0x66, 0xc8, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
17 if mixedEndianUUID != expectedMixedEndianUUID {
18 t.Errorf("mangleUUID(%s) = %x, expected %x", testUUID, mixedEndianUUID, expectedMixedEndianUUID)
19 }
20 roundTrippedUUID := unmangleUUID(mixedEndianUUID)
21 if testUUID != roundTrippedUUID {
22 t.Errorf("unmangleUUID(mangleUUID(%s)) = %s, expected input", testUUID, roundTrippedUUID)
23 }
24}
25
26func TestFreeSpaces(t *testing.T) {
27 cases := []struct {
28 name string
29 parts []*Partition
30 expected [][2]int64
31 expectedOverlap bool
32 }{
33 {"Empty", []*Partition{}, [][2]int64{{34, 2015}}, false},
34 {"OnePart", []*Partition{
35 {Type: PartitionTypeEFISystem, FirstBlock: 200, LastBlock: 1499},
36 }, [][2]int64{
37 {34, 200},
38 {1500, 2015},
39 }, false},
40 {"TwoOverlappingParts", []*Partition{
41 {Type: PartitionTypeEFISystem, FirstBlock: 200, LastBlock: 1499},
42 {Type: PartitionTypeEFISystem, FirstBlock: 1000, LastBlock: 1999},
43 }, [][2]int64{
44 {34, 200},
45 {2000, 2015},
46 }, true},
47 {"Full", []*Partition{
48 {Type: PartitionTypeEFISystem, FirstBlock: 34, LastBlock: 999},
49 {Type: PartitionTypeEFISystem, FirstBlock: 1000, LastBlock: 2014},
50 }, [][2]int64{}, false},
51 {"TwoSpacedParts", []*Partition{
52 {Type: PartitionTypeEFISystem, FirstBlock: 500, LastBlock: 899},
53 {Type: PartitionTypeEFISystem, FirstBlock: 1200, LastBlock: 1799},
54 }, [][2]int64{
55 {34, 500},
56 {900, 1200},
57 {1800, 2015},
58 }, false},
59 }
60
61 // Partitions are created manually as AddPartition calls FreeSpaces itself,
62 // which makes the test unreliable as well as making failures very hard to
63 // debug.
64 for _, c := range cases {
65 t.Run(c.name, func(t *testing.T) {
66 g := Table{
67 BlockSize: 512,
68 BlockCount: 2048, // 0.5KiB * 2048 = 1MiB
69 Partitions: c.parts,
70 }
71 fs, overlap, err := g.GetFreeSpaces()
72 if err != nil {
73 t.Fatal(err)
74 }
75 if overlap != c.expectedOverlap {
76 t.Errorf("expected overlap %v, got %v", c.expectedOverlap, overlap)
77 }
78 if len(fs) != len(c.expected) {
79 t.Fatalf("expected %v, got %v", c.expected, fs)
80 }
81 for i := range fs {
82 if fs[i] != c.expected[i] {
83 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])
84 }
85 }
86 })
87 }
88}
89
90func TestRoundTrip(t *testing.T) {
91 if os.Getenv("IN_KTEST") == "true" {
92 t.Skip("In ktest")
93 }
94 g := Table{
95 ID: uuid.NewSHA1(zeroUUID, []byte("test")),
96 BlockSize: 512,
97 BlockCount: 2048,
98 BootCode: []byte("just some test code"),
99 Partitions: []*Partition{
100 nil,
101 // This emoji is very complex and exercises UTF16 surrogate encoding
102 // and composing.
103 {Name: "Test 🏃‍♂️", FirstBlock: 10, LastBlock: 19, Type: PartitionTypeEFISystem, ID: uuid.NewSHA1(zeroUUID, []byte("test1")), Attributes: AttrNoBlockIOProto},
104 nil,
105 {Name: "Test2", FirstBlock: 20, LastBlock: 49, Type: PartitionTypeEFISystem, ID: uuid.NewSHA1(zeroUUID, []byte("test2")), Attributes: 0},
106 },
107 }
108 f, err := os.CreateTemp("", "")
109 if err != nil {
110 t.Fatalf("Failed to create temporary file: %v", err)
111 }
112 defer os.Remove(f.Name())
113 if err := Write(f, &g); err != nil {
114 t.Fatalf("Error while wrinting Table: %v", err)
115 }
116 f.Seek(0, io.SeekStart)
117 originalHash := sha256.New()
118 if _, err := io.Copy(originalHash, f); err != nil {
119 panic(err)
120 }
121
122 g2, err := Read(f, 512, 2048)
123 if err != nil {
124 t.Fatalf("Failed to read back GPT: %v", err)
125 }
126 if g2.ID != g.ID {
127 t.Errorf("Disk UUID changed when reading back: %v", err)
128 }
129 // Destroy primary GPT
130 f.Seek(1*g.BlockSize, io.SeekStart)
131 f.Write(make([]byte, 512))
132 f.Seek(0, io.SeekStart)
133 g3, err := Read(f, 512, 2048)
134 if err != nil {
135 t.Fatalf("Failed to read back GPT with primary GPT destroyed: %v", err)
136 }
137 if g3.ID != g.ID {
138 t.Errorf("Disk UUID changed when reading back: %v", err)
139 }
140 f.Seek(0, io.SeekStart)
141 if err := Write(f, g3); err != nil {
142 t.Fatalf("Error while writing back GPT: %v", err)
143 }
144 f.Seek(0, io.SeekStart)
145 rewrittenHash := sha256.New()
146 if _, err := io.Copy(rewrittenHash, f); err != nil {
147 panic(err)
148 }
149 if !bytes.Equal(originalHash.Sum([]byte{}), rewrittenHash.Sum([]byte{})) {
150 t.Errorf("Write/Read/Write test was not reproducible: %x != %x", originalHash.Sum([]byte{}), rewrittenHash.Sum([]byte{}))
151 }
152}