commit e226b37980469783df491a048e17799b366160e2
parent c8e5117b684a722c6ce7c10d5954dad3b3f81bee
Author: Jack Mordaunt <jackmordaunt.dev@gmail.com>
Date: Sun, 20 Sep 2026 08:33:55 -0300
ntfs: test the unix volume against an image file
An image opens the same way a block device does, so a file stands in for one and
the tests need neither a device nor the rights to read it.
They cover what the reader depends on and nothing else: bytes land where asked
from four offset shapes, a read running off the end reports Short_Read rather
than leaving the tail of the buffer to parse as records, and closing a clone
leaves the handle it came from readable. Both halves were checked by breaking
them first.
Diffstat:
1 file changed, 95 insertions(+), 0 deletions(-)
diff --git a/ntfs/volume_other_test.odin b/ntfs/volume_other_test.odin
@@ -0,0 +1,95 @@
+#+build !windows
+package ntfs
+
+import "core:fmt"
+import "core:os"
+import "core:slice"
+import "core:testing"
+
+// A volume is a block device or a file holding an image of one, and both open the
+// same way, so a file standing in for an image exercises every path here without a
+// device to read or the rights to read it.
+@(private = "file")
+image :: proc(t: ^testing.T, size: int) -> (path: string, content: []byte) {
+ content = make([]byte, size, context.temp_allocator)
+ for i in 0 ..< size {
+ content[i] = byte(i * 7 + 1)
+ }
+ dir, dir_err := os.temp_directory(context.temp_allocator)
+ testing.expect(t, dir_err == nil, "no temp directory")
+ path = fmt.tprintf("%s/sonar_volume_%d.img", dir, size)
+ testing.expect(t, os.write_entire_file(path, content) == nil, "could not write the image")
+ return
+}
+
+@(private = "file")
+discard :: proc(path: string) {
+ _ = os.remove(path)
+}
+
+@(test)
+test_volume_read_at_offsets :: proc(t: ^testing.T) {
+ path, content := image(t, 8192)
+ defer discard(path)
+
+ v, err := volume_open(path)
+ testing.expect_value(t, err, Error.None)
+ defer volume_close(&v)
+
+ // A whole read, a tail read, an aligned middle and a single byte: the reader
+ // works in whole clusters, so every offset it asks for is one of these shapes.
+ spans := [][2]int{{0, 8192}, {4096, 4096}, {512, 1024}, {8191, 1}}
+ for span in spans {
+ off, size := span[0], span[1]
+ buf := make([]byte, size, context.temp_allocator)
+ testing.expect_value(t, volume_read_at(&v, buf, u64(off)), Error.None)
+ testing.expectf(t, slice.equal(buf, content[off:off + size]), "wrong bytes at %d for %d", off, size)
+ }
+}
+
+@(test)
+test_volume_read_past_the_end_is_short :: proc(t: ^testing.T) {
+ path, _ := image(t, 1024)
+ defer discard(path)
+
+ v, err := volume_open(path)
+ testing.expect_value(t, err, Error.None)
+ defer volume_close(&v)
+
+ // Asking for more than is there has to say so, or the tail of the buffer would
+ // parse as records that were never read.
+ buf := make([]byte, 4096, context.temp_allocator)
+ testing.expect_value(t, volume_read_at(&v, buf, 0), Error.Short_Read)
+ testing.expect_value(t, volume_read_at(&v, buf[:1], 1024), Error.Short_Read)
+}
+
+@(test)
+test_volume_clone_reads_independently :: proc(t: ^testing.T) {
+ path, content := image(t, 2048)
+ defer discard(path)
+
+ v, err := volume_open(path)
+ testing.expect_value(t, err, Error.None)
+ defer volume_close(&v)
+
+ c, clone_err := volume_clone(&v)
+ testing.expect_value(t, clone_err, Error.None)
+
+ buf := make([]byte, 16, context.temp_allocator)
+ testing.expect_value(t, volume_read_at(&c, buf, 100), Error.None)
+ testing.expect_value(t, buf[0], content[100])
+
+ // Closing a clone must leave the handle it was made from readable, or the first
+ // worker to finish would take the rest of them with it.
+ volume_close(&c)
+ testing.expect(t, c.file == nil, "a closed volume still holds a handle")
+ testing.expect_value(t, volume_read_at(&v, buf, 100), Error.None)
+ testing.expect_value(t, buf[0], content[100])
+}
+
+@(test)
+test_volume_open_missing_path :: proc(t: ^testing.T) {
+ v, err := volume_open("/nonexistent/sonar/volume.img")
+ testing.expect_value(t, err, Error.Open_Failed)
+ testing.expect(t, v.file == nil, "a failed open returned a handle")
+}