205 lines
11 KiB
ObjectPascal
205 lines
11 KiB
ObjectPascal
unit driver.storage.metatest;
|
|
|
|
interface
|
|
procedure UnitTest;
|
|
procedure init;
|
|
|
|
implementation
|
|
uses boot.mgr, io.stdio, io.syslog, memory.heap, core.util, core.ds.cfifo,
|
|
driver.storage.types, driver.storage.mgr, driver.storage.vol.mbr,
|
|
driver.storage.vol.mgr, driver.storage.fs.fat32.types,
|
|
driver.storage.fs.fat32.vol, driver.storage.fs.fat32.labels, core.strings;
|
|
|
|
type
|
|
PMock = ^TMock;
|
|
TMock = record
|
|
Data : puint8;
|
|
Writes, FailWrite, Callbacks : uint32;
|
|
Error : TError;
|
|
OutOfBounds, FailRead : boolean;
|
|
end;
|
|
|
|
procedure dispatch(device : PStorage_Device; request : PIORequest);
|
|
var m : PMock; offset, size : uint32;
|
|
begin
|
|
m := PMock(device^.controllerId0);
|
|
if (request^.LBA >= device^.maxSectorCount) or
|
|
(request^.SectorCount > device^.maxSectorCount-request^.LBA) then begin
|
|
m^.OutOfBounds := true; complete_io(request, false, eInvalidArgument); exit;
|
|
end;
|
|
if (request^.RequestType <> ioWrite) and m^.FailRead then begin complete_io(request, false, eIOError); exit; end;
|
|
offset := request^.LBA*512; size := request^.SectorCount*512;
|
|
if request^.RequestType = ioWrite then begin
|
|
Inc(m^.Writes);
|
|
if m^.Writes = m^.FailWrite then begin complete_io(request, false, eIOError); exit; end;
|
|
memcpy(uint32(request^.Buffer), uint32(m^.Data)+offset, size);
|
|
end else memcpy(uint32(m^.Data)+offset, uint32(request^.Buffer), size);
|
|
request^.ByteCount := size;
|
|
complete_io(request, true, eNone);
|
|
end;
|
|
|
|
procedure completion(error : TError; data : pointer);
|
|
var m : PMock;
|
|
begin m := PMock(data); Inc(m^.Callbacks); m^.Error := error; end;
|
|
|
|
{ Only heap-backed synthetic storage is used. Never registers a hardware device,
|
|
never accesses a host/guest disk, and every fake request completes inline. }
|
|
procedure UnitTest;
|
|
var
|
|
device : PStorage_Device;
|
|
volume, added : PStorage_Volume;
|
|
mock : PMock;
|
|
br : PBootRecord;
|
|
mbr, before : PMaster_Boot_Record;
|
|
params : TFSFormatParams;
|
|
labelValue : TVolumeLabel;
|
|
part : TPartition_table;
|
|
passed, failed, oldCount, writes, i : uint32;
|
|
err : TError;
|
|
procedure Assert(ok : boolean; name : pchar);
|
|
begin
|
|
if ok then Inc(passed) else begin Inc(failed); io.syslog.logln('STORMETA FAIL', name); end;
|
|
end;
|
|
begin
|
|
passed := 0; failed := 0;
|
|
device := PStorage_Device(kalloc(sizeof(TStorage_Device)));
|
|
volume := PStorage_Volume(kalloc(sizeof(TStorage_Volume)));
|
|
mock := PMock(kalloc(sizeof(TMock)));
|
|
br := PBootRecord(kalloc(sizeof(TBootRecord)));
|
|
if (device = nil) or (volume = nil) or (mock = nil) or (br = nil) then begin
|
|
if device <> nil then kfree(void(device));
|
|
if volume <> nil then kfree(void(volume));
|
|
if mock <> nil then kfree(void(mock));
|
|
if br <> nil then kfree(void(br));
|
|
io.syslog.logln('STORMETA FAIL', 'test allocation'); exit;
|
|
end;
|
|
memset(uint32(device),0,sizeof(TStorage_Device));
|
|
memset(uint32(volume),0,sizeof(TStorage_Volume));
|
|
memset(uint32(mock),0,sizeof(TMock)); memset(uint32(br),0,sizeof(TBootRecord));
|
|
mock^.Data := puint8(kalloc(256*512));
|
|
memset(uint32(mock^.Data),$CC,256*512);
|
|
device^.controllerId0 := uint32(mock); device^.sectorSize := 512;
|
|
device^.maxSectorCount := 256; device^.writable := true; device^.maxActive := 1;
|
|
device^.dispatchRead := @dispatch; device^.dispatchWrite := @dispatch;
|
|
device^.requestQueue := CFIFO_New(sizeof(Pointer),4);
|
|
device^.cachedMBR := kalloc(512);
|
|
mbr := PMaster_Boot_Record(device^.cachedMBR);
|
|
memset(uint32(mbr),0,512); mbr^.boot_sector := $AA55; mbr^.signature := $12345678;
|
|
Assert(write_mbr(device,mbr) = eNone,'aliased MBR write succeeds');
|
|
Assert(PMaster_Boot_Record(mock^.Data)^.signature = $12345678,'aliased MBR bytes preserved');
|
|
before := PMaster_Boot_Record(device^.cachedMBR);
|
|
mock^.FailWrite := mock^.Writes+1;
|
|
Assert(write_mbr(device,before) = eIOError,'write failure propagated');
|
|
Assert(device^.cachedMBR = before,'failed write retains cache');
|
|
mock^.FailWrite := 0; mock^.Callbacks := 0;
|
|
write_mbr_async(device,PMaster_Boot_Record(device^.cachedMBR),@completion,mock);
|
|
Assert((mock^.Callbacks = 1) and (mock^.Error = eNone),'async MBR callback once');
|
|
Assert(PMaster_Boot_Record(mock^.Data)^.signature = $12345678,'async alias preserved');
|
|
before := PMaster_Boot_Record(device^.cachedMBR); mock^.Callbacks := 0;
|
|
mock^.FailWrite := mock^.Writes+1;
|
|
write_mbr_async(device,before,@completion,mock);
|
|
Assert((mock^.Callbacks = 1) and (mock^.Error = eIOError),'async failure callback once');
|
|
Assert(device^.cachedMBR = before,'async failure retains cache');
|
|
mock^.FailWrite := 0;
|
|
volume^.device := device; volume^.sectorStart := 64; volume^.sectorCount := 128; volume^.sectorSize := 512;
|
|
memset(uint32(@params),0,sizeof(params)); params.Version := 1;
|
|
params.Flags := FS_FORMAT_PARAM_CLUSTER_SIZE; params.ClusterSize := 1;
|
|
Assert(validate_format(volume,128,64,@params) = eNone,'valid format geometry');
|
|
params.ClusterSize := 3;
|
|
Assert(validate_format(volume,128,64,@params) = eInvalidArgument,'reject non-power-of-two clusters');
|
|
params.ClusterSize := 1; volume^.sectorCount := 33; writes := mock^.Writes;
|
|
Assert(create_volume(volume,33,64,@params) = eInvalidArgument,'reject tiny partition');
|
|
Assert(mock^.Writes = writes,'rejected format does not write');
|
|
volume^.sectorCount := 128;
|
|
for i := 1 to 6 do begin
|
|
mock^.FailWrite := mock^.Writes+i;
|
|
Assert(create_volume(volume,128,64,@params) = eIOError,'each failed format write propagated');
|
|
end;
|
|
mock^.FailWrite := 0;
|
|
Assert(create_volume(volume,128,64,@params) = eNone,'format completes');
|
|
Assert(not mock^.OutOfBounds,'all writes within device');
|
|
Assert((mock^.Data[63*512] = $CC) and (mock^.Data[192*512] = $CC),'neighbor partitions untouched');
|
|
memcpy(uint32(mock^.Data)+64*512,uint32(br),sizeof(TBootRecord));
|
|
Assert(FAT32ClusterLimit(br) = 97,'cluster bound excludes FAT padding');
|
|
Assert(identify_volume(volume),'fresh format identified');
|
|
Assert(volume^.freeSectors = 93,'free space excludes metadata and allocated directories');
|
|
mock^.FailRead := true;
|
|
Assert(not identify_volume(volume),'failed probe read cannot report success');
|
|
mock^.FailRead := false;
|
|
Assert(normalize_label(' projects ',labelValue) = eNone,'label accepts spaces');
|
|
Assert(stringEquals(@labelValue[0],'PROJECTS'),'label trims and uppercases');
|
|
Assert(normalize_label('ABCDEFGHIJKL',labelValue) = eInvalidArgument,'reject long label');
|
|
Assert(normalize_label('BAD/NAME',labelValue) = eInvalidArgument,'reject invalid label character');
|
|
writes := mock^.Writes;
|
|
Assert(write_label(volume,'BAD/NAME') = eInvalidArgument,'invalid rename rejected');
|
|
Assert(mock^.Writes = writes,'invalid label does not write');
|
|
Assert(write_label(volume,'Projects') = eNone,'write volume label');
|
|
volume^.labelText[0] := #0;
|
|
Assert(read_label(volume) = eNone,'reload label from disk');
|
|
Assert(stringEquals(@volume^.labelText[0],'PROJECTS'),'label survives cache reset');
|
|
Assert(PDirectory(uint32(mock^.Data)+97*512)^.fileName[0] = 'S','rename preserves SYSTEM directory');
|
|
{ Add a valid backup boot sector and verify all three metadata writes. }
|
|
PBootRecord(uint32(mock^.Data)+64*512)^.backupCluster := 6;
|
|
memcpy(uint32(mock^.Data)+64*512,uint32(mock^.Data)+70*512,512);
|
|
for i := 1 to 3 do begin
|
|
mock^.FailWrite := mock^.Writes+i;
|
|
Assert(write_label(volume,'NEW NAME') = eIOError,'label write failure propagated');
|
|
mock^.FailWrite := 0;
|
|
Assert(read_label(volume) = eNone,'label readable after failed rename');
|
|
Assert(stringEquals(@volume^.labelText[0],'PROJECTS'),'failed rename retains previous label');
|
|
end;
|
|
Assert(write_label(volume,'ABCDEFGHIJK') = eNone,'maximum length label');
|
|
Assert(PBootRecord(uint32(mock^.Data)+70*512)^.volumeLabel[10] = ord('K'),'backup label updated');
|
|
Assert(write_label(volume,'') = eNone,'clear label');
|
|
volume^.labelText[0] := 'X';
|
|
Assert(read_label(volume) = eNone,'reload cleared label');
|
|
Assert(volume^.labelText[0] = #0,'blank label persists');
|
|
params.Flags := params.Flags or FS_FORMAT_PARAM_LABEL;
|
|
memcpy(uint32(pchar('MY DATA')),uint32(@params.LabelText[0]),8);
|
|
Assert(create_volume(volume,128,64,@params) = eNone,'format with label');
|
|
Assert(read_label(volume) = eNone,'read format label');
|
|
Assert(stringEquals(@volume^.labelText[0],'MY DATA'),'format label persists');
|
|
Assert(PDirectory(uint32(mock^.Data)+97*512)^.attributes = $08,'format creates root volume label entry');
|
|
Assert(not mock^.OutOfBounds,'label operations stay inside volume');
|
|
oldCount := get_volume_count();
|
|
memset(uint32(@part),0,sizeof(part)); part.LBA_start := 64; part.sector_count := 128;
|
|
mock^.FailWrite := mock^.Writes+1;
|
|
Assert(add_partition(device,0,part) = eIOError,'failed partition creation reported');
|
|
Assert(get_volume_count() = oldCount,'failed create publishes no volume');
|
|
Assert(PMaster_Boot_Record(device^.cachedMBR)^.partition[0].sector_count = 0,'failed create leaves MBR cache');
|
|
mock^.FailWrite := 0;
|
|
Assert(add_partition(device,0,part) = eNone,'partition creation committed');
|
|
added := get_volume(get_volume_count()-1);
|
|
added^.lifecycleFlags := STORAGE_VOLUME_FLAG_FORMATTING;
|
|
writes := mock^.Writes;
|
|
Assert(remove_partition(device,0) = eFileInUse,'delete rejects formatting volume');
|
|
Assert(mock^.Writes = writes,'busy delete leaves disk untouched');
|
|
mock^.Callbacks := 0;
|
|
Assert(not format_volume_async(device,get_volume_count()-1,'FAT32',@params,@completion,mock),'busy format rejected');
|
|
Assert(mock^.Callbacks = 0,'false submission never calls callback');
|
|
added^.lifecycleFlags := 0;
|
|
mock^.FailWrite := mock^.Writes+1;
|
|
Assert(remove_partition(device,0) = eIOError,'delete propagates failed MBR write');
|
|
Assert(get_volume_count() = oldCount+1,'failed delete retains volume');
|
|
mock^.FailWrite := 0;
|
|
Assert(remove_partition(device,0) = eNone,'delete commits');
|
|
Assert(get_volume_count() = oldCount,'delete detaches volume after write');
|
|
kfree(device^.cachedMBR); CFIFO_Free(device^.requestQueue);
|
|
kfree(void(mock^.Data)); kfree(void(mock)); kfree(void(volume)); kfree(void(device)); kfree(void(br));
|
|
io.syslog.log('STORMETA','Passed: '); io.syslog.writeint(passed);
|
|
io.syslog.writestring(' Failed: '); io.syslog.writeintln(failed);
|
|
end;
|
|
|
|
procedure command(params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf);
|
|
begin
|
|
UnitTest;
|
|
io.stdio.bufWriteStrLn(stdout_buf,'Storage metadata regression results written to serial log.');
|
|
end;
|
|
|
|
procedure init;
|
|
begin io.stdio.registerCommand('STORMETA',@command,'Run memory-backed storage metadata regression tests'); end;
|
|
|
|
initialization
|
|
boot.mgr.registerBoot('driver.storage.metatest',@init,'Storage Metadata Tests',BOOT_MGR_BARRIER_MID);
|
|
end.
|