681 lines
24 KiB
Zig
681 lines
24 KiB
Zig
const std = @import("std");
|
|
const nexfs = @import("nexfs");
|
|
|
|
// ============================================================================
|
|
// Phase 1 Tests
|
|
// ============================================================================
|
|
|
|
test "version check" {
|
|
try std.testing.expectEqualStrings("0.1.0", nexfs.version);
|
|
}
|
|
|
|
test "config validation - valid config" {
|
|
var read_buf: [4096]u8 = undefined;
|
|
var write_buf: [4096]u8 = undefined;
|
|
var workspace: [256]u8 = undefined;
|
|
|
|
const config = nexfs.Config{
|
|
.flash = undefined,
|
|
.device_size = 1024 * 1024,
|
|
.block_size = 4096,
|
|
.block_count = 256,
|
|
.page_size = 256,
|
|
.checksum_algo = .CRC32C,
|
|
.read_buffer = &read_buf,
|
|
.write_buffer = &write_buf,
|
|
.workspace = &workspace,
|
|
.time_source = null,
|
|
.verbose = false,
|
|
};
|
|
|
|
try nexfs.validateConfigRuntime(&config);
|
|
}
|
|
|
|
test "config validation - block size not power of 2" {
|
|
var read_buf: [512]u8 = undefined;
|
|
var write_buf: [512]u8 = undefined;
|
|
var workspace: [64]u8 = undefined;
|
|
|
|
const config = nexfs.Config{
|
|
.flash = undefined,
|
|
.device_size = 2048,
|
|
.block_size = 768,
|
|
.block_count = 3,
|
|
.page_size = 64,
|
|
.checksum_algo = .CRC32C,
|
|
.read_buffer = &read_buf,
|
|
.write_buffer = &write_buf,
|
|
.workspace = &workspace,
|
|
.time_source = null,
|
|
.verbose = false,
|
|
};
|
|
|
|
const err = nexfs.validateConfigRuntime(&config);
|
|
try std.testing.expectError(nexfs.NexFSError.InvalidConfig, err);
|
|
}
|
|
|
|
test "config validation - 2048 byte blocks valid" {
|
|
var read_buf: [2048]u8 = undefined;
|
|
var write_buf: [2048]u8 = undefined;
|
|
var workspace: [256]u8 = undefined;
|
|
|
|
const config = nexfs.Config{
|
|
.flash = undefined,
|
|
.device_size = 2048,
|
|
.block_size = 2048,
|
|
.block_count = 1,
|
|
.page_size = 256,
|
|
.checksum_algo = .CRC32C,
|
|
.read_buffer = &read_buf,
|
|
.write_buffer = &write_buf,
|
|
.workspace = &workspace,
|
|
.time_source = null,
|
|
.verbose = false,
|
|
};
|
|
|
|
try nexfs.validateConfigRuntime(&config);
|
|
}
|
|
|
|
test "error types exist" {
|
|
const err1 = nexfs.NexFSError.InvalidConfig;
|
|
const err2 = nexfs.NexFSError.NotMounted;
|
|
const err3 = nexfs.NexFSError.AlreadyMounted;
|
|
|
|
try std.testing.expect(err1 != err2);
|
|
try std.testing.expect(err2 != err3);
|
|
}
|
|
|
|
test "types - block addr is u32" {
|
|
const info = @typeInfo(nexfs.BlockAddr);
|
|
try std.testing.expectEqual(32, info.int.bits);
|
|
}
|
|
|
|
test "types - inode id is u32" {
|
|
const info = @typeInfo(nexfs.InodeId);
|
|
try std.testing.expectEqual(32, info.int.bits);
|
|
}
|
|
|
|
test "types - file handle fields" {
|
|
const handle = nexfs.FileHandle{
|
|
.inode_id = 42,
|
|
.position = 12345,
|
|
.open_flags = 0,
|
|
};
|
|
try std.testing.expectEqual(42, handle.inode_id);
|
|
try std.testing.expectEqual(12345, handle.position);
|
|
}
|
|
|
|
test "checksum - crc16 non-zero" {
|
|
const data = "123456789";
|
|
const crc = nexfs.crc16(data);
|
|
try std.testing.expect(crc != 0);
|
|
}
|
|
|
|
test "checksum - crc32c non-zero" {
|
|
const data = "123456789";
|
|
const crc = nexfs.crc32c(data);
|
|
try std.testing.expect(crc != 0);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Phase 2 Tests - On-Disk Format
|
|
// ============================================================================
|
|
|
|
test "superblock size check" {
|
|
// Superblock should be 128 bytes
|
|
try std.testing.expectEqual(128, @sizeOf(nexfs.Superblock));
|
|
}
|
|
|
|
test "superblock magic number" {
|
|
try std.testing.expectEqual(0x4E455846, nexfs.NEXFS_MAGIC);
|
|
}
|
|
|
|
test "superblock validation - valid" {
|
|
var sb: nexfs.Superblock = .{
|
|
.magic = nexfs.NEXFS_MAGIC,
|
|
.version = nexfs.NEXFS_VERSION,
|
|
.generation = 0,
|
|
.block_size = 4096,
|
|
.page_size = 256,
|
|
.block_count = 256,
|
|
.data_block_count = 250,
|
|
.root_inode = 1,
|
|
.inode_table_start = 10,
|
|
.inode_table_blocks = 4,
|
|
.bam_start = 2,
|
|
.bam_blocks = 8,
|
|
.create_time = 0,
|
|
.last_mount_time = 0,
|
|
.mount_count = 0,
|
|
.flags = 0,
|
|
.reserved = 0,
|
|
._padding = .{0} ** 44,
|
|
.checksum = 0,
|
|
};
|
|
sb.computeChecksum();
|
|
try sb.validate();
|
|
}
|
|
|
|
test "superblock validation - bad magic" {
|
|
var sb: nexfs.Superblock = .{
|
|
.magic = 0x12345678, // Wrong magic
|
|
.version = nexfs.NEXFS_VERSION,
|
|
.generation = 0,
|
|
.block_size = 4096,
|
|
.page_size = 256,
|
|
.block_count = 256,
|
|
.data_block_count = 250,
|
|
.root_inode = 1,
|
|
.inode_table_start = 10,
|
|
.inode_table_blocks = 4,
|
|
.bam_start = 2,
|
|
.bam_blocks = 8,
|
|
.create_time = 0,
|
|
.last_mount_time = 0,
|
|
.mount_count = 0,
|
|
.flags = 0,
|
|
.reserved = 0,
|
|
._padding = .{0} ** 44,
|
|
.checksum = 0,
|
|
};
|
|
const err = sb.validate();
|
|
try std.testing.expectError(nexfs.NexFSError.Corruption, err);
|
|
}
|
|
|
|
test "inode size check" {
|
|
// Inode is 120 bytes (with padding for alignment)
|
|
try std.testing.expectEqual(120, @sizeOf(nexfs.Inode));
|
|
}
|
|
|
|
test "inode validation" {
|
|
var inode: nexfs.Inode = .{
|
|
.id = 1,
|
|
.file_type = .Regular,
|
|
.mode = 0o644,
|
|
.uid = 0,
|
|
.gid = 0,
|
|
.size = 0,
|
|
.block_count = 0,
|
|
.link_count = 1,
|
|
.flags = 0,
|
|
.atime = 0,
|
|
.mtime = 0,
|
|
.ctime = 0,
|
|
.inline_extent = .{ .logical_block = 0, .physical_block = 0, .length = 0, .reserved = 0 },
|
|
.extent_table = 0,
|
|
.extent_count = 0,
|
|
.reserved1 = 0,
|
|
.reserved2 = 0,
|
|
._padding = .{0} ** 24,
|
|
.checksum = 0,
|
|
};
|
|
inode.computeChecksum();
|
|
try inode.validate();
|
|
}
|
|
|
|
test "inode isValid check" {
|
|
var inode: nexfs.Inode = .{
|
|
.id = 1,
|
|
.file_type = .Regular,
|
|
.mode = 0o644,
|
|
.uid = 0,
|
|
.gid = 0,
|
|
.size = 0,
|
|
.block_count = 0,
|
|
.link_count = 1,
|
|
.flags = 0,
|
|
.atime = 0,
|
|
.mtime = 0,
|
|
.ctime = 0,
|
|
.inline_extent = .{ .logical_block = 0, .physical_block = 0, .length = 0, .reserved = 0 },
|
|
.extent_table = 0,
|
|
.extent_count = 0,
|
|
.reserved1 = 0,
|
|
.reserved2 = 0,
|
|
._padding = .{0} ** 24,
|
|
.checksum = 0,
|
|
};
|
|
try std.testing.expect(inode.isValid());
|
|
|
|
// Invalid inode
|
|
var invalid_inode: nexfs.Inode = .{
|
|
.id = 0,
|
|
.file_type = .None,
|
|
.mode = 0,
|
|
.uid = 0,
|
|
.gid = 0,
|
|
.size = 0,
|
|
.block_count = 0,
|
|
.link_count = 0,
|
|
.flags = 0,
|
|
.atime = 0,
|
|
.mtime = 0,
|
|
.ctime = 0,
|
|
.inline_extent = .{ .logical_block = 0, .physical_block = 0, .length = 0, .reserved = 0 },
|
|
.extent_table = 0,
|
|
.extent_count = 0,
|
|
.reserved1 = 0,
|
|
.reserved2 = 0,
|
|
._padding = .{0} ** 24,
|
|
.checksum = 0,
|
|
};
|
|
try std.testing.expect(!invalid_inode.isValid());
|
|
}
|
|
|
|
test "extent size check" {
|
|
// Extent should be 16 bytes
|
|
try std.testing.expectEqual(16, @sizeOf(nexfs.Extent));
|
|
}
|
|
|
|
test "dir entry size check" {
|
|
// DirEntry header is 20 bytes (without name)
|
|
try std.testing.expectEqual(20, @sizeOf(nexfs.DirEntry));
|
|
}
|
|
|
|
test "dir entry size calculation" {
|
|
// Entry with name "test" (4 chars) should be rounded up to 8-byte boundary
|
|
// DirEntry header is 20 bytes, plus 4 chars + 1 null = 25, rounded up to 32
|
|
const size1 = nexfs.dirEntrySize(4);
|
|
try std.testing.expectEqual(32, size1);
|
|
}
|
|
|
|
test "bam entry size check" {
|
|
// BAM Entry should be small
|
|
try std.testing.expect(@sizeOf(nexfs.BamEntry) <= 16);
|
|
}
|
|
|
|
test "file type enum values" {
|
|
try std.testing.expectEqual(0, @intFromEnum(nexfs.FileType.None));
|
|
try std.testing.expectEqual(1, @intFromEnum(nexfs.FileType.Regular));
|
|
try std.testing.expectEqual(2, @intFromEnum(nexfs.FileType.Directory));
|
|
}
|
|
|
|
test "max name length constant" {
|
|
try std.testing.expectEqual(255, nexfs.NEXFS_MAX_NAME_LEN);
|
|
}
|
|
|
|
test "superblock version constant" {
|
|
try std.testing.expectEqual(1, nexfs.NEXFS_VERSION);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Phase 2 Tests - format() and round-trip verification
|
|
// ============================================================================
|
|
|
|
/// Mock flash for format and mount tests.
|
|
/// Aligned to 8 bytes so we can safely cast superblock/inode pointers.
|
|
const MockFlash = struct {
|
|
data: [512 * 32]u8 align(8) = [_]u8{0xFF} ** (512 * 32),
|
|
|
|
fn readFn(ctx: *anyopaque, addr: u64, buffer: []u8) nexfs.NexFSError!usize {
|
|
const flash: *MockFlash = @ptrCast(@alignCast(ctx));
|
|
const start: usize = @intCast(addr);
|
|
if (start + buffer.len > flash.data.len) return nexfs.NexFSError.ReadFailed;
|
|
@memcpy(buffer[0..buffer.len], flash.data[start..][0..buffer.len]);
|
|
return buffer.len;
|
|
}
|
|
|
|
fn writeFn(ctx: *anyopaque, addr: u64, buffer: []const u8) nexfs.NexFSError!void {
|
|
const flash: *MockFlash = @ptrCast(@alignCast(ctx));
|
|
const start: usize = @intCast(addr);
|
|
if (start + buffer.len > flash.data.len) return nexfs.NexFSError.WriteFailed;
|
|
for (buffer, 0..) |byte, i| {
|
|
flash.data[start + i] = byte;
|
|
}
|
|
}
|
|
|
|
fn eraseFn(ctx: *anyopaque, block_addr: nexfs.BlockAddr) nexfs.NexFSError!void {
|
|
_ = block_addr;
|
|
_ = ctx;
|
|
}
|
|
|
|
fn syncFn(ctx: *anyopaque) nexfs.NexFSError!void {
|
|
_ = ctx;
|
|
}
|
|
|
|
fn interface(self: *MockFlash) nexfs.FlashInterface {
|
|
return .{
|
|
.ctx = @ptrCast(self),
|
|
.read = &readFn,
|
|
.write = &writeFn,
|
|
.erase = &eraseFn,
|
|
.sync = &syncFn,
|
|
};
|
|
}
|
|
};
|
|
|
|
test "format - writes valid superblock to block 0" {
|
|
var mock = MockFlash{};
|
|
var read_buf: [512]u8 = undefined;
|
|
var write_buf: [512]u8 = undefined;
|
|
var workspace: [64]u8 = undefined;
|
|
|
|
const config = nexfs.Config{
|
|
.flash = mock.interface(),
|
|
.device_size = 512 * 32,
|
|
.block_size = 512,
|
|
.block_count = 32,
|
|
.page_size = 64,
|
|
.checksum_algo = .CRC32C,
|
|
.read_buffer = &read_buf,
|
|
.write_buffer = &write_buf,
|
|
.workspace = &workspace,
|
|
.time_source = null,
|
|
.verbose = false,
|
|
};
|
|
|
|
var fmt_buf: [512]u8 = undefined;
|
|
try nexfs.format(&config.flash, &config, &fmt_buf);
|
|
|
|
// Read back superblock from block 0
|
|
const sb: *const nexfs.Superblock = @ptrCast(@alignCast(&mock.data[0]));
|
|
try std.testing.expectEqual(nexfs.NEXFS_MAGIC, sb.magic);
|
|
try std.testing.expectEqual(nexfs.NEXFS_VERSION, sb.version);
|
|
try std.testing.expectEqual(@as(nexfs.BlockSize, 512), sb.block_size);
|
|
try std.testing.expectEqual(@as(u32, 32), sb.block_count);
|
|
try std.testing.expectEqual(@as(nexfs.InodeId, 1), sb.root_inode);
|
|
// Validate CRC
|
|
try sb.validate();
|
|
}
|
|
|
|
test "format - writes backup superblock to block 1" {
|
|
var mock = MockFlash{};
|
|
var read_buf: [512]u8 = undefined;
|
|
var write_buf: [512]u8 = undefined;
|
|
var workspace: [64]u8 = undefined;
|
|
|
|
const config = nexfs.Config{
|
|
.flash = mock.interface(),
|
|
.device_size = 512 * 32,
|
|
.block_size = 512,
|
|
.block_count = 32,
|
|
.page_size = 64,
|
|
.checksum_algo = .CRC32C,
|
|
.read_buffer = &read_buf,
|
|
.write_buffer = &write_buf,
|
|
.workspace = &workspace,
|
|
.time_source = null,
|
|
.verbose = false,
|
|
};
|
|
|
|
var fmt_buf: [512]u8 = undefined;
|
|
try nexfs.format(&config.flash, &config, &fmt_buf);
|
|
|
|
// Backup superblock at block 1 (offset 512)
|
|
const backup: *const nexfs.Superblock = @ptrCast(@alignCast(&mock.data[512]));
|
|
try backup.validate();
|
|
// Primary and backup should be identical
|
|
const primary = mock.data[0..512];
|
|
const secondary = mock.data[512..1024];
|
|
try std.testing.expectEqualSlices(u8, primary, secondary);
|
|
}
|
|
|
|
test "format - root inode written to inode table" {
|
|
var mock = MockFlash{};
|
|
var read_buf: [512]u8 = undefined;
|
|
var write_buf: [512]u8 = undefined;
|
|
var workspace: [64]u8 = undefined;
|
|
|
|
const config = nexfs.Config{
|
|
.flash = mock.interface(),
|
|
.device_size = 512 * 32,
|
|
.block_size = 512,
|
|
.block_count = 32,
|
|
.page_size = 64,
|
|
.checksum_algo = .CRC32C,
|
|
.read_buffer = &read_buf,
|
|
.write_buffer = &write_buf,
|
|
.workspace = &workspace,
|
|
.time_source = null,
|
|
.verbose = false,
|
|
};
|
|
|
|
var fmt_buf: [512]u8 = undefined;
|
|
try nexfs.format(&config.flash, &config, &fmt_buf);
|
|
|
|
// Find inode table start from superblock
|
|
const sb: *const nexfs.Superblock = @ptrCast(@alignCast(&mock.data[0]));
|
|
const inode_offset = @as(usize, sb.inode_table_start) * 512;
|
|
const root_inode: *const nexfs.Inode = @ptrCast(@alignCast(&mock.data[inode_offset]));
|
|
|
|
try std.testing.expectEqual(@as(nexfs.InodeId, 1), root_inode.id);
|
|
try std.testing.expectEqual(nexfs.FileType.Directory, root_inode.file_type);
|
|
try std.testing.expectEqual(@as(u16, 0o755), root_inode.mode);
|
|
try std.testing.expectEqual(@as(u16, 2), root_inode.link_count);
|
|
try root_inode.validate();
|
|
}
|
|
|
|
test "format - buffer too small" {
|
|
var mock = MockFlash{};
|
|
var read_buf: [512]u8 = undefined;
|
|
var write_buf: [512]u8 = undefined;
|
|
var workspace: [64]u8 = undefined;
|
|
|
|
const config = nexfs.Config{
|
|
.flash = mock.interface(),
|
|
.device_size = 512 * 32,
|
|
.block_size = 512,
|
|
.block_count = 32,
|
|
.page_size = 64,
|
|
.checksum_algo = .CRC32C,
|
|
.read_buffer = &read_buf,
|
|
.write_buffer = &write_buf,
|
|
.workspace = &workspace,
|
|
.time_source = null,
|
|
.verbose = false,
|
|
};
|
|
|
|
var tiny_buf: [64]u8 = undefined;
|
|
const err = nexfs.format(&config.flash, &config, &tiny_buf);
|
|
try std.testing.expectError(nexfs.NexFSError.BufferTooSmall, err);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Phase 3 Tests - Block Allocator and Inode Operations
|
|
// ============================================================================
|
|
|
|
test "block allocator - init" {
|
|
const allocator = nexfs.BlockAllocator.init(10, 100, 1);
|
|
try std.testing.expectEqual(@as(nexfs.BlockAddr, 10), allocator.data_start);
|
|
try std.testing.expectEqual(@as(u32, 100), allocator.data_count);
|
|
try std.testing.expectEqual(@as(nexfs.BlockAddr, 10), allocator.next_block);
|
|
try std.testing.expectEqual(@as(u32, 1), allocator.generation);
|
|
}
|
|
|
|
test "block allocator - alloc finds free block" {
|
|
var bam_entries: [4]nexfs.BamEntry = .{
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 0, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 0, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
};
|
|
|
|
var allocator = nexfs.BlockAllocator.init(100, 4, 1);
|
|
const block = try allocator.alloc(&bam_entries);
|
|
try std.testing.expectEqual(@as(nexfs.BlockAddr, 101), block);
|
|
}
|
|
|
|
test "block allocator - alloc returns NoSpace when full" {
|
|
var bam_entries: [4]nexfs.BamEntry = .{
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
};
|
|
|
|
var allocator = nexfs.BlockAllocator.init(100, 4, 1);
|
|
const err = allocator.alloc(&bam_entries);
|
|
try std.testing.expectError(nexfs.NexFSError.NoSpace, err);
|
|
}
|
|
|
|
test "block allocator - markAllocated" {
|
|
var bam_entries: [4]nexfs.BamEntry = .{
|
|
.{ .flags = .{ .allocated = 0, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 0, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 0, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 0, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
};
|
|
|
|
var allocator = nexfs.BlockAllocator.init(100, 4, 5);
|
|
try allocator.markAllocated(&bam_entries, 102);
|
|
|
|
try std.testing.expectEqual(@as(u1, 1), bam_entries[2].flags.allocated);
|
|
try std.testing.expectEqual(@as(u32, 5), bam_entries[2].generation);
|
|
}
|
|
|
|
test "block allocator - free marks for erasure" {
|
|
var bam_entries: [4]nexfs.BamEntry = .{
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
};
|
|
|
|
var allocator = nexfs.BlockAllocator.init(100, 4, 1);
|
|
try allocator.free(&bam_entries, 101);
|
|
|
|
try std.testing.expectEqual(@as(u1, 0), bam_entries[1].flags.allocated);
|
|
try std.testing.expectEqual(@as(u1, 1), bam_entries[1].flags.needs_erase);
|
|
try std.testing.expectEqual(@as(u32, 1), bam_entries[1].erase_count);
|
|
}
|
|
|
|
test "block allocator - isFree" {
|
|
var bam_entries: [4]nexfs.BamEntry = .{
|
|
.{ .flags = .{ .allocated = 1, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 0, .bad = 0, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 0, .bad = 1, .reserved = 0, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
.{ .flags = .{ .allocated = 0, .bad = 0, .reserved = 1, .needs_erase = 0, ._reserved = 0 }, .erase_count = 0, .generation = 0, .reserved = 0, ._reserved1 = 0, ._reserved2 = 0 },
|
|
};
|
|
|
|
var allocator = nexfs.BlockAllocator.init(100, 4, 1);
|
|
try std.testing.expect(!allocator.isFree(&bam_entries, 100)); // allocated
|
|
try std.testing.expect(allocator.isFree(&bam_entries, 101)); // free
|
|
try std.testing.expect(!allocator.isFree(&bam_entries, 102)); // bad
|
|
try std.testing.expect(!allocator.isFree(&bam_entries, 103)); // reserved
|
|
}
|
|
|
|
test "inode table - alloc creates valid inode" {
|
|
var mock = MockFlash{};
|
|
// Pre-erase the flash (set to zeros for empty inodes)
|
|
@memset(&mock.data, 0);
|
|
|
|
// Use aligned buffer for inode operations
|
|
var read_buf: [512]u8 align(8) = undefined;
|
|
|
|
const flash = mock.interface();
|
|
var inode: nexfs.Inode = undefined;
|
|
|
|
const id = try nexfs.InodeTable.alloc(
|
|
&flash,
|
|
10, // inode_table_start
|
|
4, // inode_table_blocks
|
|
512, // block_size
|
|
.Regular,
|
|
0o644,
|
|
&inode,
|
|
&read_buf,
|
|
);
|
|
|
|
try std.testing.expectEqual(@as(nexfs.InodeId, 1), id);
|
|
try std.testing.expectEqual(nexfs.FileType.Regular, inode.file_type);
|
|
try std.testing.expectEqual(@as(u16, 0o644), inode.mode);
|
|
try std.testing.expect(inode.isValid());
|
|
}
|
|
|
|
test "inode table - write and load roundtrip" {
|
|
var mock = MockFlash{};
|
|
@memset(&mock.data, 0);
|
|
|
|
var read_buf: [512]u8 align(8) = undefined;
|
|
var write_buf: [512]u8 align(8) = undefined;
|
|
|
|
const flash = mock.interface();
|
|
|
|
// Create and write an inode
|
|
var inode: nexfs.Inode = .{
|
|
.id = 5,
|
|
.file_type = .Directory,
|
|
.mode = 0o755,
|
|
.uid = 1000,
|
|
.gid = 1000,
|
|
.size = 4096,
|
|
.block_count = 8,
|
|
.link_count = 2,
|
|
.flags = 0,
|
|
.atime = 1234567890,
|
|
.mtime = 1234567890,
|
|
.ctime = 1234567890,
|
|
.inline_extent = .{ .logical_block = 0, .physical_block = 100, .length = 8, .reserved = 0 },
|
|
.extent_table = 0,
|
|
.extent_count = 0,
|
|
.reserved1 = 0,
|
|
.reserved2 = 0,
|
|
._padding = .{0} ** 24,
|
|
.checksum = 0,
|
|
};
|
|
inode.computeChecksum();
|
|
|
|
try nexfs.InodeTable.write(&flash, 10, 512, &inode, &write_buf);
|
|
|
|
// Load it back
|
|
var loaded: nexfs.Inode = undefined;
|
|
try nexfs.InodeTable.load(&flash, 10, 512, 5, &loaded, &read_buf);
|
|
|
|
try std.testing.expectEqual(inode.id, loaded.id);
|
|
try std.testing.expectEqual(inode.file_type, loaded.file_type);
|
|
try std.testing.expectEqual(inode.mode, loaded.mode);
|
|
try std.testing.expectEqual(inode.uid, loaded.uid);
|
|
try std.testing.expectEqual(inode.size, loaded.size);
|
|
}
|
|
|
|
test "inode table - delete clears inode" {
|
|
var mock = MockFlash{};
|
|
@memset(&mock.data, 0);
|
|
|
|
var read_buf: [512]u8 align(8) = undefined;
|
|
var write_buf: [512]u8 align(8) = undefined;
|
|
|
|
const flash = mock.interface();
|
|
|
|
// Create and write an inode
|
|
var inode: nexfs.Inode = .{
|
|
.id = 3,
|
|
.file_type = .Regular,
|
|
.mode = 0o644,
|
|
.uid = 0,
|
|
.gid = 0,
|
|
.size = 0,
|
|
.block_count = 0,
|
|
.link_count = 1,
|
|
.flags = 0,
|
|
.atime = 0,
|
|
.mtime = 0,
|
|
.ctime = 0,
|
|
.inline_extent = .{ .logical_block = 0, .physical_block = 0, .length = 0, .reserved = 0 },
|
|
.extent_table = 0,
|
|
.extent_count = 0,
|
|
.reserved1 = 0,
|
|
.reserved2 = 0,
|
|
._padding = .{0} ** 24,
|
|
.checksum = 0,
|
|
};
|
|
inode.computeChecksum();
|
|
|
|
try nexfs.InodeTable.write(&flash, 10, 512, &inode, &write_buf);
|
|
|
|
// Verify it exists first
|
|
var loaded: nexfs.Inode = undefined;
|
|
try nexfs.InodeTable.load(&flash, 10, 512, 3, &loaded, &read_buf);
|
|
try std.testing.expect(loaded.isValid());
|
|
|
|
// Delete it
|
|
try nexfs.InodeTable.delete(&flash, 10, 512, 3, &write_buf);
|
|
|
|
// Verify it's cleared by reading raw bytes (id should be 0)
|
|
const addr = @as(u64, 10) * 512 + (3 - 1) * @sizeOf(nexfs.Inode);
|
|
_ = try flash.read(flash.ctx, addr, read_buf[0..@sizeOf(nexfs.Inode)]);
|
|
const cleared: *const nexfs.Inode = @ptrCast(@alignCast(&read_buf));
|
|
try std.testing.expectEqual(@as(nexfs.InodeId, 0), cleared.id);
|
|
try std.testing.expect(!cleared.isValid());
|
|
}
|