blob: 2237f388fd3c867e0544deedb5dc25632a0f0c19 [file] [log] [blame]
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +00001package fat32
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "math/rand"
8 "os"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000015 "golang.org/x/sys/unix"
16)
17
18func TestKernelInterop(t *testing.T) {
19 if os.Getenv("IN_KTEST") != "true" {
20 t.Skip("Not in ktest")
21 }
22
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000023 type testCase struct {
24 name string
25 setup func(root *Inode) error
26 validate func(t *testing.T) error
27 }
28
29 // Random timestamp in UTC, divisible by 10ms
Jan Schär79595192024-11-11 14:55:56 +010030 testTimestamp1 := time.Date(2022, 03, 04, 5, 6, 7, 10_000_000, time.UTC)
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000031 // Random timestamp in UTC, divisible by 2s
32 testTimestamp2 := time.Date(2022, 03, 04, 5, 6, 8, 0, time.UTC)
33 // Random timestamp in UTC, divisible by 10ms
Jan Schär79595192024-11-11 14:55:56 +010034 testTimestamp3 := time.Date(2052, 03, 02, 5, 6, 7, 10_000_000, time.UTC)
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000035 // Random timestamp in UTC, divisible by 2s
36 testTimestamp4 := time.Date(2052, 10, 04, 5, 3, 4, 0, time.UTC)
37
38 testContent1 := "testcontent1"
39
40 tests := []testCase{
41 {
42 name: "SimpleFolder",
43 setup: func(root *Inode) error {
44 root.Children = []*Inode{{
45 Name: "testdir",
46 Attrs: AttrDirectory,
47 CreateTime: testTimestamp1,
48 ModTime: testTimestamp2,
49 }}
50 return nil
51 },
52 validate: func(t *testing.T) error {
53 var stat unix.Statx_t
54 if err := unix.Statx(0, "/dut/testdir", 0, unix.STATX_TYPE|unix.STATX_MTIME|unix.STATX_BTIME, &stat); err != nil {
55 availableFiles, err := os.ReadDir("/dut")
56 var availableFileNames []string
57 for _, f := range availableFiles {
58 availableFileNames = append(availableFileNames, f.Name())
59 }
60 if err != nil {
61 t.Fatalf("Failed to list filesystem root directory: %v", err)
62 }
63 t.Fatalf("Failed to stat output: %v (available: %v)", err, strings.Join(availableFileNames, ", "))
64 }
65 if stat.Mode&unix.S_IFDIR == 0 {
66 t.Errorf("testdir is expected to be a directory, but has mode %v", stat.Mode)
67 }
68 btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
Jan Schär79595192024-11-11 14:55:56 +010069 if !btime.Equal(testTimestamp1) {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +000070 t.Errorf("testdir btime expected %v, got %v", testTimestamp1, btime)
71 }
72 mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))
73 if !mtime.Equal(testTimestamp2) {
74 t.Errorf("testdir mtime expected %v, got %v", testTimestamp2, mtime)
75 }
76 return nil
77 },
78 },
79 {
80 name: "SimpleFile",
81 setup: func(root *Inode) error {
82 root.Children = []*Inode{{
83 Name: "testfile",
84 CreateTime: testTimestamp3,
85 ModTime: testTimestamp4,
86 Content: strings.NewReader(testContent1),
87 }}
88 return nil
89 },
90 validate: func(t *testing.T) error {
91 var stat unix.Statx_t
92 if err := unix.Statx(0, "/dut/testfile", 0, unix.STATX_TYPE|unix.STATX_MTIME|unix.STATX_BTIME, &stat); err != nil {
93 t.Fatalf("failed to stat output: %v", err)
94 }
95 if stat.Mode&unix.S_IFREG == 0 {
96 t.Errorf("testfile is expected to be a file, but has mode %v", stat.Mode)
97 }
98 btime := time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec))
Jan Schär79595192024-11-11 14:55:56 +010099 if !btime.Equal(testTimestamp3) {
Lorenz Brunbd2ce6d2022-07-22 00:00:13 +0000100 t.Errorf("testfile ctime expected %v, got %v", testTimestamp3, btime)
101 }
102 mtime := time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec))
103 if !mtime.Equal(testTimestamp4) {
104 t.Errorf("testfile mtime expected %v, got %v", testTimestamp3, mtime)
105 }
106 contents, err := os.ReadFile("/dut/testfile")
107 if err != nil {
108 t.Fatalf("failed to read back test file: %v", err)
109 }
110 if string(contents) != testContent1 {
111 t.Errorf("testfile contains %x, got %x", contents, []byte(testContent1))
112 }
113 return nil
114 },
115 },
116 {
117 name: "FolderHierarchy",
118 setup: func(i *Inode) error {
119 i.Children = []*Inode{{
120 Name: "l1",
121 Attrs: AttrDirectory,
122 CreateTime: testTimestamp1,
123 ModTime: testTimestamp2,
124 Children: []*Inode{{
125 Name: "l2",
126 Attrs: AttrDirectory,
127 CreateTime: testTimestamp1,
128 ModTime: testTimestamp2,
129 }},
130 }}
131 return nil
132 },
133 validate: func(t *testing.T) error {
134 dirInfo, err := os.ReadDir("/dut/l1")
135 if err != nil {
136 t.Fatalf("Failed to read top-level directory: %v", err)
137 }
138 require.Len(t, dirInfo, 1, "more subdirs than expected")
139 require.Equal(t, "l2", dirInfo[0].Name(), "unexpected subdir")
140 require.True(t, dirInfo[0].IsDir(), "l1 not a directory")
141 subdirInfo, err := os.ReadDir("/dut/l1/l2")
142 assert.NoError(t, err, "cannot read empty subdir")
143 require.Len(t, subdirInfo, 0, "unexpected subdirs in empty directory")
144 return nil
145 },
146 },
147 {
148 name: "LargeFile",
149 setup: func(i *Inode) error {
150 content := make([]byte, 6500)
151 io.ReadFull(rand.New(rand.NewSource(1)), content)
152 i.Children = []*Inode{{
153 Name: "test.bin",
154 Content: bytes.NewReader(content),
155 }}
156 return nil
157 },
158 validate: func(t *testing.T) error {
159 var stat unix.Stat_t
160 err := unix.Stat("/dut/test.bin", &stat)
161 assert.NoError(t, err, "failed to stat file")
162 require.EqualValues(t, 6500, stat.Size, "wrong size")
163 file, err := os.Open("/dut/test.bin")
164 assert.NoError(t, err, "failed to open test file")
165 defer file.Close()
166 r := io.LimitReader(rand.New(rand.NewSource(1)), 6500) // Random but deterministic data
167 expected, _ := io.ReadAll(r)
168 actual, err := io.ReadAll(file)
169 assert.NoError(t, err, "failed to read test file")
170 assert.Equal(t, expected, actual, "content not identical")
171 return nil
172 },
173 },
174 {
175 name: "Unicode",
176 setup: func(i *Inode) error {
177 i.Children = []*Inode{{
178 Name: "✨😂", // Really exercise that UTF-16 conversion
179 Content: strings.NewReader("😂"),
180 }}
181 return nil
182 },
183 validate: func(t *testing.T) error {
184 file, err := os.Open("/dut/✨😂")
185 if err != nil {
186 availableFiles, err := os.ReadDir("/dut")
187 var availableFileNames []string
188 for _, f := range availableFiles {
189 availableFileNames = append(availableFileNames, f.Name())
190 }
191 if err != nil {
192 t.Fatalf("Failed to list filesystem root directory: %v", err)
193 }
194 t.Fatalf("Failed to open unicode file: %v (available files: %v)", err, strings.Join(availableFileNames, ", "))
195 }
196 defer file.Close()
197 contents, err := io.ReadAll(file)
198 if err != nil {
199 t.Errorf("Wrong content: expected %x, got %x", []byte("😂"), contents)
200 }
201 return nil
202 },
203 },
204 {
205 name: "MultipleMetaClusters",
206 setup: func(root *Inode) error {
207 // Only test up to 2048 files as Linux gets VERY slow if going
208 // up to the maximum of approximately 32K
209 for i := 0; i < 2048; i++ {
210 root.Children = append(root.Children, &Inode{
211 Name: fmt.Sprintf("verylongtestfilename%d", i),
212 Content: strings.NewReader("random test content"),
213 })
214 }
215 return nil
216 },
217 validate: func(t *testing.T) error {
218 files, err := os.ReadDir("/dut")
219 if err != nil {
220 t.Errorf("failed to list directory: %v", err)
221 }
222 if len(files) != 2048 {
223 t.Errorf("wrong number of files: expected %d, got %d", 2048, len(files))
224 }
225 return nil
226 },
227 },
228 }
229
230 for _, test := range tests {
231 t.Run(test.name, func(t *testing.T) {
232 file, err := os.OpenFile("/dev/ram0", os.O_WRONLY|os.O_TRUNC, 0644)
233 if err != nil {
234 t.Fatalf("failed to create test image: %v", err)
235 }
236 size, err := unix.IoctlGetInt(int(file.Fd()), unix.BLKGETSIZE64)
237 if err != nil {
238 t.Fatalf("failed to get ramdisk size: %v", err)
239 }
240 blockSize, err := unix.IoctlGetInt(int(file.Fd()), unix.BLKBSZGET)
241 if err != nil {
242 t.Fatalf("failed to get ramdisk block size: %v", err)
243 }
244 defer file.Close()
245 rootInode := Inode{
246 Attrs: AttrDirectory,
247 }
248 if err := test.setup(&rootInode); err != nil {
249 t.Fatalf("setup failed: %v", err)
250 }
251 if err := WriteFS(file, rootInode, Options{
252 ID: 1234,
253 Label: "KTEST",
254 BlockSize: uint16(blockSize),
255 BlockCount: uint32(size / blockSize),
256 }); err != nil {
257 t.Fatalf("failed to write fileystem: %v", err)
258 }
259 _ = file.Close()
260 if err := os.MkdirAll("/dut", 0755); err != nil {
261 t.Error(err)
262 }
263 // TODO(lorenz): Set CONFIG_FAT_DEFAULT_UTF8 for Monogon Kernel
264 if err := unix.Mount("/dev/ram0", "/dut", "vfat", unix.MS_NOEXEC|unix.MS_NODEV, "utf8=1"); err != nil {
265 t.Fatal(err)
266 }
267 defer unix.Unmount("/dut", 0)
268 test.validate(t)
269 })
270
271 }
272}