blob: df4e8aed7151fee21049e2e81d8dd374c2317f35 [file] [log] [blame]
Jan Schära6da1712024-08-21 15:12:11 +02001package blockdev
2
3import (
4 "bytes"
5 "io"
6 "os"
7 "slices"
8 "testing"
9)
10
11func errIsNil(err error) bool {
12 return err == nil
13}
14func errIsEOF(err error) bool {
15 return err == io.EOF
16}
17func errIsReadFailed(err error) bool {
18 return err != nil && err != io.EOF
19}
20
21// ValidateBlockDev tests all methods of the BlockDev interface. This way, all
22// implementations can be tested without duplicating test code. This also
23// ensures that all implementations behave consistently.
24func ValidateBlockDev(t *testing.T, blk BlockDev, blockCount, blockSize, optimalBlockSize int64) {
25 if blk.BlockCount() != blockCount {
26 t.Errorf("Expected block count %d, got %d", blockCount, blk.BlockCount())
27 }
28 if blk.BlockSize() != blockSize {
29 t.Errorf("Expected block size %d, got %d", blockSize, blk.BlockSize())
30 }
31 if blk.OptimalBlockSize() != optimalBlockSize {
32 t.Errorf("Expected optimal block size %d, got %d", optimalBlockSize, blk.OptimalBlockSize())
33 }
34 size := blockCount * blockSize
35
36 // ReadAt
37 checkBlockDevOp(t, blk, func(content []byte) {
38 normalErr := errIsNil
39 if size < 3+8 {
40 normalErr = errIsEOF
41 }
42 readAtTests := []struct {
43 name string
44 offset, len int64
45 expectedErr func(error) bool
46 }{
47 {"empty start", 0, 0, errIsNil},
48 {"empty end", size, 0, errIsNil},
49 {"normal", 3, 8, normalErr},
50 {"ends past the end", 1, size, errIsEOF},
51 {"offset negative", -1, 2, errIsReadFailed},
52 {"starts at the end", size, 8, errIsEOF},
53 {"starts past the end", size + 4, 8, errIsEOF},
54 }
55 for _, tt := range readAtTests {
56 t.Run("read "+tt.name, func(t *testing.T) {
57 buf := make([]byte, tt.len)
58 n, err := blk.ReadAt(buf, tt.offset)
59 if !tt.expectedErr(err) {
60 t.Errorf("unexpected error %v", err)
61 }
62 expected := []byte{}
63 if tt.offset >= 0 && tt.offset <= size {
64 expected = content[tt.offset:min(tt.offset+tt.len, size)]
65 }
66 if n != len(expected) {
67 t.Errorf("got n = %d, expected %d", n, len(expected))
68 }
69 if !slices.Equal(buf[:n], expected) {
70 t.Errorf("read unexpected data")
71 }
72 })
73 }
74 })
75
76 // WriteAt
77 writeAtTests := []struct {
78 name string
79 offset int64
80 data string
81 ok bool
82 }{
83 {"empty start", 0, "", true},
84 {"empty end", size, "", true},
85 {"normal", 3, "abcdef", size >= 9},
86 {"ends at the end", size - 4, "abcd", size >= 4},
87 {"ends past the end", size - 4, "abcde", false},
88 {"offset negative", -1, "abc", false},
89 {"starts at the end", size, "abc", false},
90 {"starts past the end", size + 4, "abc", false},
91 }
92 for _, tt := range writeAtTests {
93 t.Run("write "+tt.name, func(t *testing.T) {
94 checkBlockDevOp(t, blk, func(content []byte) {
95 n, err := blk.WriteAt([]byte(tt.data), tt.offset)
96 if (err == nil) != tt.ok {
97 t.Errorf("expected error %v, got %v", tt.ok, err)
98 }
99 expectedN := 0
100 if tt.offset >= 0 && tt.offset < size {
101 expectedN = copy(content[tt.offset:], tt.data)
102 }
103 if n != expectedN {
104 t.Errorf("got n = %d, expected %d; err: %v", n, expectedN, err)
105 }
106 })
107 })
108 }
109
110 // Zero
111 zeroTests := []struct {
112 name string
113 start, end int64
114 ok bool
115 }{
116 {"empty range start", 0, 0, true},
117 {"empty range end", size, size, true},
118 {"full", 0, size, true},
119 {"partial", blockSize, 3 * blockSize, blockCount >= 3},
120 {"negative start", -blockSize, blockSize, false},
121 {"start after end", 2 * blockSize, blockSize, false},
122 {"unaligned start", 1, blockSize, false},
123 {"unaligned end", 0, 1, false},
124 }
125 for _, tt := range zeroTests {
126 t.Run("zero "+tt.name, func(t *testing.T) {
127 checkBlockDevOp(t, blk, func(content []byte) {
128 err := blk.Zero(tt.start, tt.end)
129 if (err == nil) != tt.ok {
130 t.Errorf("expected error %v, got %v", tt.ok, err)
131 }
132 if tt.ok {
133 for i := tt.start; i < tt.end; i++ {
134 content[i] = 0
135 }
136 }
137 })
138 })
139 }
140
141 // Discard
142 for _, tt := range zeroTests {
143 t.Run("discard "+tt.name, func(t *testing.T) {
144 checkBlockDevOp(t, blk, func(content []byte) {
145 err := blk.Discard(tt.start, tt.end)
146 if (err == nil) != tt.ok {
147 t.Errorf("expected error %v, got %v", tt.ok, err)
148 }
149 if tt.ok {
150 n, err := blk.ReadAt(content[tt.start:tt.end], tt.start)
151 if n != int(tt.end-tt.start) {
152 t.Errorf("read returned %d, %v", n, err)
153 }
154 }
155 })
156 })
157 }
158
159 // Sync
160 checkBlockDevOp(t, blk, func(content []byte) {
161 err := blk.Sync()
162 if err != nil {
163 t.Errorf("Sync failed: %v", err)
164 }
165 })
166}
167
168// checkBlockDevOp is a helper for testing operations on a blockdev. It fills
169// the blockdev with a pattern, then calls f with a slice containing the
170// pattern, and afterwards reads the blockdev to compare against the expected
171// content. f should modify the slice to the content expected afterwards.
172func checkBlockDevOp(t *testing.T, blk BlockDev, f func(content []byte)) {
173 t.Helper()
174
175 testContent := make([]byte, blk.BlockCount()*blk.BlockSize())
176 for i := range testContent {
177 testContent[i] = '1' + byte(i%9)
178 }
179 n, err := blk.WriteAt(testContent, 0)
180 if n != len(testContent) || err != nil {
181 t.Fatalf("WriteAt = %d, %v; expected %d, nil", n, err, len(testContent))
182 }
183 f(testContent)
184 afterContent := make([]byte, len(testContent))
185 n, err = blk.ReadAt(afterContent, 0)
186 if n != len(afterContent) || (err != nil && err != io.EOF) {
187 t.Fatalf("ReadAt = %d, %v; expected %d, (nil or EOF)", n, err, len(afterContent))
188 }
189 if !slices.Equal(afterContent, testContent) {
190 t.Errorf("Unexpected content after operation")
191 }
192}
193
194func TestReadWriteSeeker_Seek(t *testing.T) {
195 // Verifies that NewRWS's Seeker behaves like bytes.NewReader
196 br := bytes.NewReader([]byte("foobar"))
197 m := MustNewMemory(2, 3)
198 rws := NewRWS(m)
199 n, err := rws.Write([]byte("foobar"))
200 if n != 6 || err != nil {
201 t.Errorf("Write = %v, %v; want 6, nil", n, err)
202 }
203
204 for _, whence := range []int{io.SeekStart, io.SeekCurrent, io.SeekEnd} {
205 for offset := int64(-7); offset <= 7; offset++ {
206 brOff, brErr := br.Seek(offset, whence)
207 rwsOff, rwsErr := rws.Seek(offset, whence)
208 if (brErr != nil) != (rwsErr != nil) || brOff != rwsOff {
209 t.Errorf("For whence %d, offset %d: bytes.Reader.Seek = (%v, %v) != ReadWriteSeeker.Seek = (%v, %v)",
210 whence, offset, brOff, brErr, rwsOff, rwsErr)
211 }
212 }
213 }
214
215 // And verify we can just seek past the end and get an EOF
216 got, err := rws.Seek(100, io.SeekStart)
217 if err != nil || got != 100 {
218 t.Errorf("Seek = %v, %v; want 100, nil", got, err)
219 }
220
221 n, err = rws.Read(make([]byte, 10))
222 if n != 0 || err != io.EOF {
223 t.Errorf("Read = %v, %v; want 0, EOF", n, err)
224 }
225}
226
227func TestNewSection(t *testing.T) {
228 tests := []struct {
229 name string
230 blockSize int64
231 count int64
232 startBlock int64
233 endBlock int64
234 ok bool
235 sectionCount int64
236 }{
237 {name: "empty underlying", blockSize: 8, count: 0, startBlock: 0, endBlock: 0, ok: true, sectionCount: 0},
238 {name: "empty section", blockSize: 8, count: 5, startBlock: 2, endBlock: 2, ok: true, sectionCount: 0},
239 {name: "partial section", blockSize: 8, count: 15, startBlock: 1, endBlock: 11, ok: true, sectionCount: 10},
240 {name: "full section", blockSize: 8, count: 15, startBlock: 0, endBlock: 15, ok: true, sectionCount: 15},
241 {name: "negative start", blockSize: 8, count: 15, startBlock: -1, endBlock: 11, ok: false},
242 {name: "start after end", blockSize: 8, count: 15, startBlock: 6, endBlock: 5, ok: false},
243 {name: "end out of bounds", blockSize: 8, count: 15, startBlock: 6, endBlock: 16, ok: false},
244 }
245 for _, tt := range tests {
246 t.Run(tt.name, func(t *testing.T) {
247 m := MustNewMemory(tt.blockSize, tt.count)
248 s, err := NewSection(m, tt.startBlock, tt.endBlock)
249 if (err == nil) != tt.ok {
250 t.Errorf("NewSection: expected %v, got %v", tt.ok, err)
251 }
252 if err == nil {
253 checkBlockDevOp(t, m, func(content []byte) {
254 ValidateBlockDev(t, s, tt.sectionCount, tt.blockSize, tt.blockSize)
255
256 // Check that content outside the section has not changed.
257 start := tt.startBlock * tt.blockSize
258 end := tt.endBlock * tt.blockSize
259 n, err := m.ReadAt(content[start:end], start)
260 if n != int(end-start) {
261 t.Errorf("read returned %d, %v", n, err)
262 }
263 })
264 }
265 })
266 }
267}
268
269type MemoryWithGenericZero struct {
270 *Memory
271}
272
273func (m *MemoryWithGenericZero) Zero(startByte, endByte int64) error {
274 return GenericZero(m, startByte, endByte)
275}
276
277func TestGenericZero(t *testing.T) {
278 if os.Getenv("IN_KTEST") == "true" {
279 t.Skip("In ktest")
280 }
281 // Use size larger than the 16 MiB buffer size in GenericZero.
282 blockSize := int64(512)
283 blockCount := int64(35 * 1024)
284 m, err := NewMemory(blockSize, blockCount)
285 if err != nil {
286 t.Errorf("NewMemory: %v", err)
287 }
288 b := &MemoryWithGenericZero{m}
289 if err == nil {
290 ValidateBlockDev(t, b, blockCount, blockSize, blockSize)
291 }
292}
293
294func TestNewMemory(t *testing.T) {
295 tests := []struct {
296 name string
297 blockSize int64
298 count int64
299 ok bool
300 }{
301 {name: "normal", blockSize: 64, count: 9, ok: true},
302 {name: "count 0", blockSize: 8, count: 0, ok: true},
303 {name: "count negative", blockSize: 8, count: -1, ok: false},
304 {name: "blockSize not a power of 2", blockSize: 9, count: 5, ok: false},
305 {name: "blockSize 0", blockSize: 0, count: 5, ok: false},
306 {name: "blockSize negative", blockSize: -1, count: 5, ok: false},
307 }
308 for _, tt := range tests {
309 t.Run(tt.name, func(t *testing.T) {
310 m, err := NewMemory(tt.blockSize, tt.count)
311 if (err == nil) != tt.ok {
312 t.Errorf("NewMemory: expected %v, got %v", tt.ok, err)
313 }
314 if err == nil {
315 ValidateBlockDev(t, m, tt.count, tt.blockSize, tt.blockSize)
316 }
317 })
318 }
319}