MODULE DiskTests;
IMPORT
Machine, Streams, Random, Kernel, Commands, Disks, Partitions, Lib := PartitionsLib, Strings;
TYPE
TestDataBase = OBJECT(Lib.Operation);
VAR
buffer : POINTER TO ARRAY OF CHAR;
sectorsPerTransfer : LONGINT;
PROCEDURE SetParameters*(sectorsPerTransfer : LONGINT);
BEGIN
SELF.sectorsPerTransfer := sectorsPerTransfer;
END SetParameters;
PROCEDURE ValidParameters*() : BOOLEAN;
BEGIN
IF sectorsPerTransfer < 1 THEN ReportError("SectorsPerTransfer must be >= 1"); RETURN FALSE; END;
IF disk.device.blockSize MOD 256 # 0 THEN ReportError("Device blocksize MOD 256 MUST BE 0"); RETURN FALSE; END;
RETURN TRUE;
END ValidParameters;
END TestDataBase;
TYPE
TestDataWriter* = OBJECT(TestDataBase);
PROCEDURE FillWithTestData(VAR buffer : ARRAY OF CHAR);
VAR i : LONGINT;
BEGIN
FOR i := 0 TO LEN(buffer) - 1 DO buffer[i] := CHR(i MOD 256); END;
END FillWithTestData;
PROCEDURE DoOperation*;
VAR pos, num, nbrOfBlocks, blocksWritten, res : LONGINT; temp : ARRAY 256 OF CHAR;
BEGIN
SetStatus(state.status, "Writing test data...", 0, 0, disk.table[partition].size, TRUE);
NEW(buffer, disk.device.blockSize * sectorsPerTransfer);
FillWithTestData(buffer^);
pos := disk.table[partition].start; num := sectorsPerTransfer; nbrOfBlocks := disk.table[partition].size;
LOOP
IF num > nbrOfBlocks - blocksWritten THEN num := nbrOfBlocks - blocksWritten; END;
IF ~alive OR (num = 0) THEN EXIT; END;
disk.device.Transfer(Disks.Write, pos, num, buffer^, 0, res);
IF res # Disks.Ok THEN Lib.GetTransferError(disk.device, Disks.Write, pos, res, temp); ReportError(temp); END;
INC(pos, num); INC(blocksWritten, num);
SetCurrentProgress(blocksWritten);
END;
IF alive THEN
result.String("Test data written to partition "); result.String(diskpartString);
ELSE
result.String("Operation aborted");
END;
END DoOperation;
PROCEDURE &Init*(disk :Lib.Disk; partition : LONGINT; out : Streams.Writer);
BEGIN
Init^(disk, partition, out);
name := "WriteTestData"; desc := "Write test data to partition"; locktype := Lib.WriterLock;
END Init;
END TestDataWriter;
TYPE
TestDataChecker* = OBJECT(TestDataBase);
PROCEDURE DoOperation*;
VAR
pos, num, nbrOfBlocks, blocksRead, res : LONGINT; string, nbr : ARRAY 128 OF CHAR;
expected, found, foundAt : LONGINT;
BEGIN
SetStatus(state.status, "Verifying test data...", 0, 0, disk.table[partition].size, TRUE);
NEW(buffer, disk.device.blockSize * sectorsPerTransfer);
pos := disk.table[partition].start; num := sectorsPerTransfer; nbrOfBlocks := disk.table[partition].size;
LOOP
IF num > nbrOfBlocks - blocksRead THEN num := nbrOfBlocks - blocksRead; END;
IF ~alive OR (num = 0) THEN EXIT; END;
disk.device.Transfer(Disks.Read, pos, num, buffer^, 0, res);
IF res # Disks.Ok THEN
Lib.GetTransferError(disk.device, Disks.Read, pos, res, string); ReportError(string);
ELSIF ~TestDataIsCorrect(0, num, disk.device.blockSize, buffer^, expected, found, foundAt) THEN
string := "Verification of block at pos "; Strings.IntToStr(pos, nbr); Strings.Append(string, nbr);
Strings.Append(string, ", Expected value: "); Strings.IntToStr(expected, nbr); Strings.Append(string, nbr);
Strings.Append(string, ", found: "); Strings.IntToStr(found, nbr); Strings.Append(string, nbr);
Strings.Append(string, " at index: "); Strings.IntToStr(foundAt, nbr); Strings.Append(string, nbr);
ReportError(string);
END;
INC(pos, num); INC(blocksRead, num);
SetCurrentProgress(blocksRead);
END;
IF alive THEN
result.String("Test data verified on partition "); result.String(diskpartString); result.String(" - ");
IF state.errorCount = 0 THEN result.String("No "); END;
result.String("Errors found.");
END;
END DoOperation;
PROCEDURE &Init*(disk :Lib.Disk; partition : LONGINT; out : Streams.Writer);
BEGIN
Init^(disk, partition, out);
name := "CheckTestData"; desc := "Verify test data on partition"; locktype := Lib.ReaderLock;
END Init;
END TestDataChecker;
TYPE
ZeroWriter* = OBJECT(TestDataWriter);
PROCEDURE FillWithTestData*(VAR buffer : ARRAY OF CHAR);
VAR i : LONGINT;
BEGIN
FOR i := 0 TO LEN(buffer) - 1 DO buffer[i] := 0X; END;
END FillWithTestData;
PROCEDURE & Init*(disk : Lib.Disk; partition : LONGINT; out : Streams.Writer);
BEGIN
Init^(disk, partition, out);
name := "ZeroWriter"; desc := "Fill with zeros partition"; locktype := Lib.WriterLock;
END Init;
END ZeroWriter;
TYPE
DiskTest* = OBJECT(Lib.Operation)
VAR
doRead, doWrite, testData : BOOLEAN;
nbrOfTests, maxNbrOfSectors, maxOffset : LONGINT;
start, size : LONGINT;
offset : LONGINT;
testCount : LONGINT;
testedOffsets : POINTER TO ARRAY OF BOOLEAN;
testedSectors : POINTER TO ARRAY OF BOOLEAN;
blocksRead : HUGEINT;
buffer : POINTER TO ARRAY OF CHAR;
random : Random.Generator;
PROCEDURE SetParameters*(doRead, doWrite, testData : BOOLEAN; nbrOfTests, maxNbrOfSectors, maxOffset : LONGINT);
BEGIN
SELF.doRead := doRead; SELF.doWrite := doWrite; SELF.testData := testData;
SELF.nbrOfTests := nbrOfTests; SELF.maxNbrOfSectors := maxNbrOfSectors; SELF.maxOffset := maxOffset;
END SetParameters;
PROCEDURE ValidParameters*() : BOOLEAN;
BEGIN
IF ~doRead & ~doWrite THEN ReportError("Either read or write tests must be done"); RETURN FALSE; END;
IF maxNbrOfSectors < 1 THEN ReportError("MaxNbrOfSectors must be >= 1"); RETURN FALSE; END;
IF maxOffset < 0 THEN ReportError("MaxOffset must be >= 0"); RETURN FALSE; END;
RETURN TRUE;
END ValidParameters;
PROCEDURE WriteTestSettings;
BEGIN
info.String("Test Settings:"); info.Ln;
info.String(" Number of Tests: "); IF nbrOfTests > 0 THEN info.Int(nbrOfTests, 0); ELSE info.String("Endless Loop Mode"); END; info.Ln;
info.String(" Read Tests: "); IF doRead THEN info.String("Yes"); ELSE info.String("No"); END; info.Ln;
info.String(" Write Tests: "); IF doWrite THEN info.String("Yes"); ELSE info.String("No"); END; info.Ln;
info.String(" Verify Reads using Test Data: "); IF testData THEN info.String("Yes"); ELSE info.String("No"); END; info.Ln;
info.String(" Max. Sectors per Transfer: "); info.Int(maxNbrOfSectors, 0); info.Ln;
info.String(" Max. Offset into Client Buffer: "); info.Int(maxOffset, 0); info.Ln;
info.Ln;
END WriteTestSettings;
PROCEDURE WriteSummary;
VAR i, val : LONGINT;
PROCEDURE WriteB(b: HUGEINT; w : Streams.Writer);
VAR suffix: ARRAY 3 OF CHAR;
BEGIN
IF b > 1024*1024*1024 THEN suffix := "GB"; b := Machine.DivH(b, 1024*1024*1024);
ELSIF b > 1024*1024 THEN suffix := "MB"; b := Machine.DivH(b, 1024*1024);
ELSIF b > 1024 THEN suffix := "KB"; b := Machine.DivH(b, 1024);
ELSE suffix := "B";
END;
w.Int(SHORT(b), 0); w.String(suffix);
END WriteB;
BEGIN
info.String("Test Summary:"); info.Ln;
info.String(" "); info.Int(testCount, 0); info.String(" Test Runs done"); info.Ln;
IF testedOffsets # NIL THEN
val := 0; FOR i := 0 TO LEN(testedOffsets)-1 DO IF testedOffsets[i] THEN INC(val); END; END;
info.String(" Offset Coverage: "); info.FloatFix(100.0 * val / LEN(testedOffsets), 5, 2, 0); info.Char("%"); info.Ln;
END;
IF testedSectors # NIL THEN
val := 0; FOR i := 0 TO LEN(testedSectors)-1 DO IF testedSectors[i] THEN INC(val); END; END;
info.String(" Transfer Sizes Coverage: "); info.FloatFix(100.0 * val / LEN(testedSectors), 5, 2, 0); info.Char("%"); info.Ln;
END;
info.String(" Total amount of data read: "); WriteB(Machine.MulH(blocksRead, disk.device.blockSize), info); info.Ln;
END WriteSummary;
PROCEDURE PerformStep;
VAR pos, num, res, expected, found, foundAt : LONGINT; string, nbr : ARRAY 128 OF CHAR;
BEGIN
num := random.Dice(maxNbrOfSectors) + 1;
IF maxNbrOfSectors > 1 THEN testedSectors[num - 1] := TRUE; END;
pos := start + random.Dice(size - num);
disk.device.Transfer(Disks.Read, pos, num, buffer^, offset, res);
IF res # Disks.Ok THEN
Lib.GetTransferError(disk.device, Disks.Write, pos, res, string); ReportError(string);
ELSE
INC (blocksRead, num);
IF testData & ~TestDataIsCorrect(offset, num, disk.device.blockSize, buffer^, expected, found, foundAt) THEN
string := "Data Verification failed (Pos: "; Strings.IntToStr(pos, nbr); Strings.Append(string, nbr);
Strings.Append(string, ", Num: "); Strings.IntToStr(num, nbr); Strings.Append(string, nbr);
Strings.Append(string, ", Offset: "); Strings.IntToStr(offset, nbr); Strings.Append(string, nbr);
Strings.Append(string, ": ");
Strings.Append(string, "Expected value: "); Strings.IntToStr(expected, nbr); Strings.Append(string, nbr);
Strings.Append(string, ", found value: "); Strings.IntToStr(found, nbr); Strings.Append(string, nbr);
Strings.Append(string, " at index: "); Strings.IntToStr(foundAt, nbr); Strings.Append(string, nbr);
Strings.Append(string, ")");
ReportError(string);
END;
END;
END PerformStep;
PROCEDURE DoOperation*;
BEGIN
start := disk.table[partition].start; size := disk.table[partition].size;
NEW(buffer, maxNbrOfSectors * disk.device.blockSize + maxOffset);
WriteTestSettings;
IF nbrOfTests > 0 THEN SetStatus(state.status, "Testing...", 0, 0, nbrOfTests, TRUE);
ELSE SetStatus(state.status, "Testing (loop mode)...", 0, 0, 0, FALSE);
END;
IF maxOffset > 0 THEN NEW(testedOffsets, maxOffset + 1); END;
IF maxNbrOfSectors > 1 THEN NEW(testedSectors, maxNbrOfSectors); END;
testCount := 0; offset := 0;
LOOP
IF ~alive THEN EXIT END;
IF nbrOfTests > 0 THEN
SetCurrentProgress(testCount);
IF testCount >= nbrOfTests THEN EXIT; END;
END;
PerformStep;
IF maxOffset > 0 THEN testedOffsets[offset] := TRUE; offset := (offset + 1) MOD (maxOffset + 1); END;
INC(testCount);
END;
WriteSummary;
IF alive THEN
result.String("Finished testing partition "); result.String(diskpartString); result.String(" - ");
IF state.errorCount = 0 THEN result.String("No "); END;
result.String("Errors found");
END;
END DoOperation;
PROCEDURE &Init*(disk :Lib.Disk; partition : LONGINT; out : Streams.Writer);
BEGIN
Init^(disk, partition, out);
name := "DiskTester"; desc := "Perform disk test on partition"; locktype := Lib.ReaderLock;
NEW(random); random.InitSeed(Kernel.GetTicks());
END Init;
END DiskTest;
PROCEDURE TestDataIsCorrect*(offset, numblocks, blocksize : LONGINT; CONST buffer : ARRAY OF CHAR; VAR expected, found, foundAt : LONGINT) : BOOLEAN;
VAR i : LONGINT;
BEGIN
ASSERT(LEN(buffer) >= numblocks * blocksize + offset);
ASSERT(blocksize MOD 256 = 0);
FOR i := 0 TO numblocks * blocksize - 1 DO
IF ORD(buffer[i + offset]) # i MOD 256 THEN
expected := i MOD 256; found := ORD(buffer[i + offset]); foundAt := i;
RETURN FALSE;
END;
END;
RETURN TRUE;
END TestDataIsCorrect;
PROCEDURE WriteTestData*(context : Commands.Context);
VAR selection : Lib.Selection; testDataWriter : TestDataWriter;
BEGIN
IF Partitions.GetSelection(context, FALSE, selection) THEN
NEW(testDataWriter, selection.disk, selection.partition, context.out);
testDataWriter.SetParameters(1);
testDataWriter.SetStart;
ELSE
END;
END WriteTestData;
PROCEDURE VerifyTestData*(context : Commands.Context);
VAR selection : Lib.Selection; testDataChecker : TestDataChecker;
BEGIN
IF Partitions.GetSelection(context, FALSE, selection) THEN
NEW(testDataChecker, selection.disk, selection.partition, context.out);
testDataChecker.SetParameters(1);
testDataChecker.SetStart;
ELSE
END;
END VerifyTestData;
PROCEDURE WriteZeros*(context : Commands.Context);
VAR selection : Lib.Selection; zeroWriter : ZeroWriter;
BEGIN
IF Partitions.GetSelection(context, FALSE, selection) THEN
NEW(zeroWriter, selection.disk, selection.partition, context.out);
zeroWriter.SetParameters(1);
zeroWriter.SetStart;
ELSE
END;
END WriteZeros;
PROCEDURE Test*(context : Commands.Context);
VAR selection : Lib.Selection; diskTest : DiskTest;
BEGIN
IF Partitions.GetSelection(context, FALSE, selection) THEN
NEW(diskTest, selection.disk, selection.partition, context.out);
diskTest.SetParameters(TRUE, FALSE, FALSE, 100, 100, 0);
diskTest.SetStart;
ELSE
END;
END Test;
PROCEDURE TransferBlocks*(context : Commands.Context);
VAR
selection : Lib.Selection;
string : ARRAY 32 OF CHAR; dev : Disks.Device;
op, block, numblocks, res : LONGINT;
buffer : POINTER TO ARRAY OF CHAR;
BEGIN
IF Partitions.GetSelection(context, FALSE, selection) THEN
context.arg.SkipWhitespace; context.arg.String(string);
IF string = "READ" THEN op := Disks.Read;
ELSIF string = "WRITE" THEN op := Disks.Write;
ELSE context.error.String("DiskTests: Expected READ|WRITE parameter."); context.error.Ln; RETURN;
END;
IF ~context.arg.GetInteger(block, FALSE) OR (block < 0) THEN context.error.String("DiskTests: Expected block parameter."); context.error.Ln; RETURN; END;
IF ~context.arg.GetInteger(numblocks, FALSE) OR (block < 0) THEN context.error.String("DiskTests: Expected numblocks parameter."); context.error.Ln; RETURN; END;
dev := selection.disk.device;
context.out.String("DiskTests: ");
IF op = Disks.Read THEN context.out.String("Reading "); ELSE context.out.String(" Writing "); END;
context.out.Int(numblocks, 0); context.out.String(" blocks at offset "); context.out.Int(block, 0);
IF op = Disks.Read THEN context.out.String(" from "); ELSE context.out.String(" to "); END;
context.out.String(" partition "); context.out.String(dev.name); context.out.String("#"); context.out.Int(selection.partition, 0);
context.out.String("... "); context.out.Update;
dev.Open(res);
IF res = Disks.Ok THEN
IF dev.table[selection.partition].size - block < numblocks THEN
context.error.String("DiskTests: Numblocks too big. Would cross partition. Aborting test."); context.error.Ln;
ELSE
NEW(buffer, numblocks * dev.blockSize);
dev.Transfer(op, block, numblocks, buffer^, 0, res);
ShowDiskres(res, context.out); context.error.Ln;
END;
dev.Close(res);
ELSE
context.error.String("DiskTests: Could not open device "); context.error.String(dev.name);
context.error.String(": "); ShowDiskres(res, context.out); context.error.Ln;
END;
ELSE context.error.String("DiskTests: TransferBlocks: Device not found."); context.error.Ln;
END;
END TransferBlocks;
PROCEDURE ShowDiskres(res : LONGINT; out : Streams.Writer);
BEGIN
IF res = Disks.Ok THEN out.String("Ok");
ELSIF res = Disks.MediaChanged THEN out.String("MediaChanged");
ELSIF res = Disks.WriteProtected THEN out.String("WriteProtected");
ELSIF res = Disks.Unsupported THEN out.String("Unsupported");
ELSIF res = Disks.DeviceInUse THEN out.String("DeviceInUse");
ELSIF res = Disks.MediaMissing THEN out.String("MediaMissing");
ELSE out.String("Unknown (res: "); out.Int(res, 0); out.String(")");
END;
END ShowDiskres;
END DiskTests.
DiskTests.WriteTestData USB0#1 ~ SystemTools.Free DiskTests ~
DiskTests.VerifyTestData USB0#1 ~
DiskTests.Test USB0#1 ~
DiskTests.TransferBlocks USB0#1 READ 0 6 ~
UsbInfo.TraceOn Custom~
UsbInfo.TraceNone ~
Partitions.ShowOps ~
Partitions.ShowOps detail ~
Partitions.Abort 1 ~