DiskTests.Mod 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. MODULE DiskTests; (** AUTHOR "staubesv"; PURPOSE "Simple block device tests"; *)
  2. (**
  3. * Usage:
  4. *
  5. * DiskTests.WriteTestData dev#part ~ fills the specified partition with test data
  6. * DiskTests.VerifyTestData dev#part ~ checks whether the test data can be correctly read
  7. * DiskTests.WriteZeros dev#part ~ fills the specifed partition with zeros
  8. * DiskTests.Test dev#part ~ tests the specified partition
  9. * DiskTests.TransferBlocks dev#part "READ"|"WRITE" block numblocks ~ (TUI only)
  10. *
  11. * WMPartitions.Open ~ opens the graphical front-end
  12. *
  13. * History:
  14. *
  15. * 28.02.2006 First release (staubesv)
  16. *)
  17. IMPORT
  18. Streams, Random, Kernel, Commands, Disks, Partitions, Lib := PartitionsLib, Strings;
  19. TYPE
  20. TestDataBase = OBJECT(Lib.Operation);
  21. VAR
  22. buffer : POINTER TO ARRAY OF CHAR;
  23. sectorsPerTransfer : LONGINT;
  24. PROCEDURE SetParameters*(sectorsPerTransfer : LONGINT);
  25. BEGIN
  26. SELF.sectorsPerTransfer := sectorsPerTransfer;
  27. END SetParameters;
  28. PROCEDURE ValidParameters*() : BOOLEAN;
  29. BEGIN
  30. IF sectorsPerTransfer < 1 THEN ReportError("SectorsPerTransfer must be >= 1"); RETURN FALSE; END;
  31. IF disk.device.blockSize MOD 256 # 0 THEN ReportError("Device blocksize MOD 256 MUST BE 0"); RETURN FALSE; END;
  32. RETURN TRUE;
  33. END ValidParameters;
  34. END TestDataBase;
  35. TYPE
  36. (** Fills partition with test data *)
  37. TestDataWriter* = OBJECT(TestDataBase);
  38. PROCEDURE FillWithTestData*(VAR buffer : ARRAY OF CHAR);
  39. VAR i : LONGINT;
  40. BEGIN
  41. FOR i := 0 TO LEN(buffer) - 1 DO buffer[i] := CHR(i MOD 256); END;
  42. END FillWithTestData;
  43. PROCEDURE DoOperation*;
  44. VAR pos, num, nbrOfBlocks, blocksWritten, res : LONGINT; temp : ARRAY 256 OF CHAR;
  45. BEGIN
  46. SetStatus(state.status, "Writing test data...", 0, 0, disk.table[partition].size, TRUE);
  47. NEW(buffer, disk.device.blockSize * sectorsPerTransfer);
  48. FillWithTestData(buffer^);
  49. pos := disk.table[partition].start; num := sectorsPerTransfer; nbrOfBlocks := disk.table[partition].size;
  50. LOOP
  51. IF num > nbrOfBlocks - blocksWritten THEN num := nbrOfBlocks - blocksWritten; END;
  52. IF ~alive OR (num = 0) THEN EXIT; END;
  53. disk.device.Transfer(Disks.Write, pos, num, buffer^, 0, res);
  54. IF res # Disks.Ok THEN Lib.GetTransferError(disk.device, Disks.Write, pos, res, temp); ReportError(temp); END;
  55. INC(pos, num); INC(blocksWritten, num);
  56. SetCurrentProgress(blocksWritten);
  57. END;
  58. IF alive THEN
  59. result.String("Test data written to partition "); result.String(diskpartString);
  60. ELSE
  61. result.String("Operation aborted");
  62. END;
  63. END DoOperation;
  64. PROCEDURE &Init*(disk :Lib.Disk; partition : LONGINT; out : Streams.Writer);
  65. BEGIN
  66. Init^(disk, partition, out);
  67. name := "WriteTestData"; desc := "Write test data to partition"; locktype := Lib.WriterLock;
  68. END Init;
  69. END TestDataWriter;
  70. TYPE
  71. (** Checks whether the test data written by the WriteTestData object can be read back correctly *)
  72. TestDataChecker* = OBJECT(TestDataBase);
  73. PROCEDURE DoOperation*;
  74. VAR
  75. pos, num, nbrOfBlocks, blocksRead, res : LONGINT; string, nbr : ARRAY 128 OF CHAR;
  76. expected, found, foundAt : LONGINT;
  77. BEGIN
  78. SetStatus(state.status, "Verifying test data...", 0, 0, disk.table[partition].size, TRUE);
  79. NEW(buffer, disk.device.blockSize * sectorsPerTransfer);
  80. pos := disk.table[partition].start; num := sectorsPerTransfer; nbrOfBlocks := disk.table[partition].size;
  81. LOOP
  82. IF num > nbrOfBlocks - blocksRead THEN num := nbrOfBlocks - blocksRead; END;
  83. IF ~alive OR (num = 0) THEN EXIT; END;
  84. disk.device.Transfer(Disks.Read, pos, num, buffer^, 0, res);
  85. IF res # Disks.Ok THEN
  86. Lib.GetTransferError(disk.device, Disks.Read, pos, res, string); ReportError(string);
  87. ELSIF ~TestDataIsCorrect(0, num, disk.device.blockSize, buffer^, expected, found, foundAt) THEN
  88. string := "Verification of block at pos "; Strings.IntToStr(pos, nbr); Strings.Append(string, nbr);
  89. Strings.Append(string, ", Expected value: "); Strings.IntToStr(expected, nbr); Strings.Append(string, nbr);
  90. Strings.Append(string, ", found: "); Strings.IntToStr(found, nbr); Strings.Append(string, nbr);
  91. Strings.Append(string, " at index: "); Strings.IntToStr(foundAt, nbr); Strings.Append(string, nbr);
  92. ReportError(string);
  93. END;
  94. INC(pos, num); INC(blocksRead, num);
  95. SetCurrentProgress(blocksRead);
  96. END;
  97. IF alive THEN
  98. result.String("Test data verified on partition "); result.String(diskpartString); result.String(" - ");
  99. IF state.errorCount = 0 THEN result.String("No "); END;
  100. result.String("Errors found.");
  101. END;
  102. END DoOperation;
  103. PROCEDURE &Init*(disk :Lib.Disk; partition : LONGINT; out : Streams.Writer);
  104. BEGIN
  105. Init^(disk, partition, out);
  106. name := "CheckTestData"; desc := "Verify test data on partition"; locktype := Lib.ReaderLock;
  107. END Init;
  108. END TestDataChecker;
  109. TYPE
  110. ZeroWriter* = OBJECT(TestDataWriter);
  111. PROCEDURE FillWithTestData*(VAR buffer : ARRAY OF CHAR);
  112. VAR i : LONGINT;
  113. BEGIN
  114. FOR i := 0 TO LEN(buffer) - 1 DO buffer[i] := 0X; END;
  115. END FillWithTestData;
  116. PROCEDURE & Init*(disk : Lib.Disk; partition : LONGINT; out : Streams.Writer);
  117. BEGIN
  118. Init^(disk, partition, out);
  119. name := "ZeroWriter"; desc := "Fill with zeros partition"; locktype := Lib.WriterLock;
  120. END Init;
  121. END ZeroWriter;
  122. TYPE
  123. (**
  124. * Test a partition
  125. *)
  126. DiskTest* = OBJECT(Lib.Operation)
  127. VAR
  128. (* parameters *)
  129. doRead, doWrite, testData : BOOLEAN;
  130. nbrOfTests, maxNbrOfSectors, maxOffset : LONGINT;
  131. start, size : LONGINT; (* First block of partition and size of the partition *)
  132. offset : LONGINT; (* currently used offset into client buffer *)
  133. (* Coverage information *)
  134. testCount : LONGINT;
  135. testedOffsets : POINTER TO ARRAY OF BOOLEAN;
  136. testedSectors : POINTER TO ARRAY OF BOOLEAN;
  137. blocksRead : HUGEINT;
  138. buffer : POINTER TO ARRAY OF CHAR;
  139. random : Random.Generator;
  140. PROCEDURE SetParameters*(doRead, doWrite, testData : BOOLEAN; nbrOfTests, maxNbrOfSectors, maxOffset : LONGINT);
  141. BEGIN
  142. SELF.doRead := doRead; SELF.doWrite := doWrite; SELF.testData := testData;
  143. SELF.nbrOfTests := nbrOfTests; SELF.maxNbrOfSectors := maxNbrOfSectors; SELF.maxOffset := maxOffset;
  144. END SetParameters;
  145. PROCEDURE ValidParameters*() : BOOLEAN;
  146. BEGIN
  147. IF ~doRead & ~doWrite THEN ReportError("Either read or write tests must be done"); RETURN FALSE; END;
  148. IF maxNbrOfSectors < 1 THEN ReportError("MaxNbrOfSectors must be >= 1"); RETURN FALSE; END;
  149. IF maxOffset < 0 THEN ReportError("MaxOffset must be >= 0"); RETURN FALSE; END;
  150. RETURN TRUE;
  151. END ValidParameters;
  152. PROCEDURE WriteTestSettings;
  153. BEGIN
  154. info.String("Test Settings:"); info.Ln;
  155. info.String(" Number of Tests: "); IF nbrOfTests > 0 THEN info.Int(nbrOfTests, 0); ELSE info.String("Endless Loop Mode"); END; info.Ln;
  156. info.String(" Read Tests: "); IF doRead THEN info.String("Yes"); ELSE info.String("No"); END; info.Ln;
  157. info.String(" Write Tests: "); IF doWrite THEN info.String("Yes"); ELSE info.String("No"); END; info.Ln;
  158. info.String(" Verify Reads using Test Data: "); IF testData THEN info.String("Yes"); ELSE info.String("No"); END; info.Ln;
  159. info.String(" Max. Sectors per Transfer: "); info.Int(maxNbrOfSectors, 0); info.Ln;
  160. info.String(" Max. Offset into Client Buffer: "); info.Int(maxOffset, 0); info.Ln;
  161. info.Ln;
  162. END WriteTestSettings;
  163. PROCEDURE WriteSummary;
  164. VAR i, val : LONGINT;
  165. PROCEDURE WriteB(b: HUGEINT; w : Streams.Writer);
  166. VAR suffix: ARRAY 3 OF CHAR;
  167. BEGIN
  168. IF b > 1024*1024*1024 THEN suffix := "GB"; b := b DIV (1024*1024*1024);
  169. ELSIF b > 1024*1024 THEN suffix := "MB"; b := b DIV (1024*1024);
  170. ELSIF b > 1024 THEN suffix := "KB"; b := b DIV 1024;
  171. ELSE suffix := "B";
  172. END;
  173. w.Int(SHORT(b), 0); w.String(suffix);
  174. END WriteB;
  175. BEGIN
  176. info.String("Test Summary:"); info.Ln;
  177. info.String(" "); info.Int(testCount, 0); info.String(" Test Runs done"); info.Ln;
  178. IF testedOffsets # NIL THEN
  179. val := 0; FOR i := 0 TO LEN(testedOffsets)-1 DO IF testedOffsets[i] THEN INC(val); END; END;
  180. info.String(" Offset Coverage: "); info.FloatFix(100.0 * val / LEN(testedOffsets), 5, 2, 0); info.Char("%"); info.Ln;
  181. END;
  182. IF testedSectors # NIL THEN
  183. val := 0; FOR i := 0 TO LEN(testedSectors)-1 DO IF testedSectors[i] THEN INC(val); END; END;
  184. info.String(" Transfer Sizes Coverage: "); info.FloatFix(100.0 * val / LEN(testedSectors), 5, 2, 0); info.Char("%"); info.Ln;
  185. END;
  186. info.String(" Total amount of data read: "); WriteB(blocksRead * disk.device.blockSize, info); info.Ln;
  187. END WriteSummary;
  188. PROCEDURE PerformStep;
  189. VAR pos, num, res, expected, found, foundAt : LONGINT; string, nbr : ARRAY 128 OF CHAR;
  190. BEGIN
  191. num := random.Dice(maxNbrOfSectors) + 1;
  192. IF maxNbrOfSectors > 1 THEN testedSectors[num - 1] := TRUE; END;
  193. pos := start + random.Dice(size - num);
  194. disk.device.Transfer(Disks.Read, pos, num, buffer^, offset, res);
  195. IF res # Disks.Ok THEN
  196. Lib.GetTransferError(disk.device, Disks.Write, pos, res, string); ReportError(string);
  197. ELSE
  198. INC (blocksRead, num);
  199. IF testData & ~TestDataIsCorrect(offset, num, disk.device.blockSize, buffer^, expected, found, foundAt) THEN
  200. string := "Data Verification failed (Pos: "; Strings.IntToStr(pos, nbr); Strings.Append(string, nbr);
  201. Strings.Append(string, ", Num: "); Strings.IntToStr(num, nbr); Strings.Append(string, nbr);
  202. Strings.Append(string, ", Offset: "); Strings.IntToStr(offset, nbr); Strings.Append(string, nbr);
  203. Strings.Append(string, ": ");
  204. Strings.Append(string, "Expected value: "); Strings.IntToStr(expected, nbr); Strings.Append(string, nbr);
  205. Strings.Append(string, ", found value: "); Strings.IntToStr(found, nbr); Strings.Append(string, nbr);
  206. Strings.Append(string, " at index: "); Strings.IntToStr(foundAt, nbr); Strings.Append(string, nbr);
  207. Strings.Append(string, ")");
  208. ReportError(string);
  209. END;
  210. END;
  211. END PerformStep;
  212. PROCEDURE DoOperation*;
  213. BEGIN
  214. start := disk.table[partition].start; size := disk.table[partition].size;
  215. NEW(buffer, maxNbrOfSectors * disk.device.blockSize + maxOffset);
  216. WriteTestSettings;
  217. IF nbrOfTests > 0 THEN SetStatus(state.status, "Testing...", 0, 0, nbrOfTests, TRUE);
  218. ELSE SetStatus(state.status, "Testing (loop mode)...", 0, 0, 0, FALSE);
  219. END;
  220. IF maxOffset > 0 THEN NEW(testedOffsets, maxOffset + 1); END;
  221. IF maxNbrOfSectors > 1 THEN NEW(testedSectors, maxNbrOfSectors); END;
  222. testCount := 0; offset := 0;
  223. LOOP
  224. IF ~alive THEN EXIT END;
  225. IF nbrOfTests > 0 THEN
  226. SetCurrentProgress(testCount);
  227. IF testCount >= nbrOfTests THEN EXIT; END;
  228. END;
  229. PerformStep;
  230. IF maxOffset > 0 THEN testedOffsets[offset] := TRUE; offset := (offset + 1) MOD (maxOffset + 1); END;
  231. INC(testCount);
  232. END;
  233. WriteSummary;
  234. IF alive THEN
  235. result.String("Finished testing partition "); result.String(diskpartString); result.String(" - ");
  236. IF state.errorCount = 0 THEN result.String("No "); END;
  237. result.String("Errors found");
  238. END;
  239. END DoOperation;
  240. PROCEDURE &Init*(disk :Lib.Disk; partition : LONGINT; out : Streams.Writer);
  241. BEGIN
  242. Init^(disk, partition, out);
  243. name := "DiskTester"; desc := "Perform disk test on partition"; locktype := Lib.ReaderLock;
  244. NEW(random); random.InitSeed(Kernel.GetTicks());
  245. END Init;
  246. END DiskTest;
  247. PROCEDURE TestDataIsCorrect*(offset, numblocks, blocksize : LONGINT; CONST buffer : ARRAY OF CHAR; VAR expected, found, foundAt : LONGINT) : BOOLEAN;
  248. VAR i : LONGINT;
  249. BEGIN
  250. ASSERT(LEN(buffer) >= numblocks * blocksize + offset);
  251. ASSERT(blocksize MOD 256 = 0); (* Otherwise test data used will not work *)
  252. FOR i := 0 TO numblocks * blocksize - 1 DO
  253. IF ORD(buffer[i + offset]) # i MOD 256 THEN
  254. expected := i MOD 256; found := ORD(buffer[i + offset]); foundAt := i;
  255. RETURN FALSE;
  256. END;
  257. END;
  258. RETURN TRUE;
  259. END TestDataIsCorrect;
  260. (** Fill partition with test data *)
  261. PROCEDURE WriteTestData*(context : Commands.Context); (** dev#part ~ *)
  262. VAR selection : Lib.Selection; testDataWriter : TestDataWriter;
  263. BEGIN
  264. IF Partitions.GetSelection(context, FALSE, selection) THEN
  265. NEW(testDataWriter, selection.disk, selection.partition, context.out);
  266. testDataWriter.SetParameters(1);
  267. testDataWriter.SetStart;
  268. ELSE (* skip; error written to <w> by ScanOpenPart *)
  269. END;
  270. END WriteTestData;
  271. (** Fill partition with test data *)
  272. PROCEDURE VerifyTestData*(context : Commands.Context); (** dev#part ~ *)
  273. VAR selection : Lib.Selection; testDataChecker : TestDataChecker;
  274. BEGIN
  275. IF Partitions.GetSelection(context, FALSE, selection) THEN
  276. NEW(testDataChecker, selection.disk, selection.partition, context.out);
  277. testDataChecker.SetParameters(1);
  278. testDataChecker.SetStart;
  279. ELSE (* skip; error written to <w> by ScanOpenPart *)
  280. END;
  281. END VerifyTestData;
  282. (** Fill partition with zeros *)
  283. PROCEDURE WriteZeros*(context : Commands.Context); (** dev#part ~ *)
  284. VAR selection : Lib.Selection; zeroWriter : ZeroWriter;
  285. BEGIN
  286. IF Partitions.GetSelection(context, FALSE, selection) THEN
  287. NEW(zeroWriter, selection.disk, selection.partition, context.out);
  288. zeroWriter.SetParameters(1);
  289. zeroWriter.SetStart;
  290. ELSE (* skip; error written to <w> by ScanOpenPart *)
  291. END;
  292. END WriteZeros;
  293. (** Test the specified partition *)
  294. PROCEDURE Test*(context : Commands.Context); (** dev#part ~ *)
  295. VAR selection : Lib.Selection; diskTest : DiskTest;
  296. BEGIN
  297. IF Partitions.GetSelection(context, FALSE, selection) THEN
  298. NEW(diskTest, selection.disk, selection.partition, context.out);
  299. diskTest.SetParameters(TRUE, FALSE, FALSE, 100, 100, 0);
  300. diskTest.SetStart;
  301. ELSE (* skip; error written to <w> by ScanOpenPart *)
  302. END;
  303. END Test;
  304. (** Read/write the specified number of sectors from/to the specified paritition starting at the specified sector *)
  305. PROCEDURE TransferBlocks*(context : Commands.Context); (** dev#part "READ"|"WRITE" block numblocks ~ *)
  306. VAR
  307. selection : Lib.Selection;
  308. string : ARRAY 32 OF CHAR; dev : Disks.Device;
  309. op, block, numblocks, res : LONGINT;
  310. buffer : POINTER TO ARRAY OF CHAR;
  311. BEGIN
  312. IF Partitions.GetSelection(context, FALSE, selection) THEN
  313. context.arg.SkipWhitespace; context.arg.String(string);
  314. IF string = "READ" THEN op := Disks.Read;
  315. ELSIF string = "WRITE" THEN op := Disks.Write;
  316. ELSE context.error.String("DiskTests: Expected READ|WRITE parameter."); context.error.Ln; RETURN;
  317. END;
  318. IF ~context.arg.GetInteger(block, FALSE) OR (block < 0) THEN context.error.String("DiskTests: Expected block parameter."); context.error.Ln; RETURN; END;
  319. IF ~context.arg.GetInteger(numblocks, FALSE) OR (block < 0) THEN context.error.String("DiskTests: Expected numblocks parameter."); context.error.Ln; RETURN; END;
  320. dev := selection.disk.device;
  321. context.out.String("DiskTests: ");
  322. IF op = Disks.Read THEN context.out.String("Reading "); ELSE context.out.String(" Writing "); END;
  323. context.out.Int(numblocks, 0); context.out.String(" blocks at offset "); context.out.Int(block, 0);
  324. IF op = Disks.Read THEN context.out.String(" from "); ELSE context.out.String(" to "); END;
  325. context.out.String(" partition "); context.out.String(dev.name); context.out.String("#"); context.out.Int(selection.partition, 0);
  326. context.out.String("... "); context.out.Update;
  327. dev.Open(res);
  328. IF res = Disks.Ok THEN
  329. IF dev.table[selection.partition].size - block < numblocks THEN
  330. context.error.String("DiskTests: Numblocks too big. Would cross partition. Aborting test."); context.error.Ln;
  331. ELSE
  332. NEW(buffer, numblocks * dev.blockSize);
  333. dev.Transfer(op, block, numblocks, buffer^, 0, res);
  334. ShowDiskres(res, context.out); context.error.Ln;
  335. END;
  336. dev.Close(res); (* ignore res *)
  337. ELSE
  338. context.error.String("DiskTests: Could not open device "); context.error.String(dev.name);
  339. context.error.String(": "); ShowDiskres(res, context.out); context.error.Ln;
  340. END;
  341. ELSE context.error.String("DiskTests: TransferBlocks: Device not found."); context.error.Ln;
  342. END;
  343. END TransferBlocks;
  344. PROCEDURE ShowDiskres(res : LONGINT; out : Streams.Writer);
  345. BEGIN
  346. IF res = Disks.Ok THEN out.String("Ok");
  347. ELSIF res = Disks.MediaChanged THEN out.String("MediaChanged");
  348. ELSIF res = Disks.WriteProtected THEN out.String("WriteProtected");
  349. ELSIF res = Disks.Unsupported THEN out.String("Unsupported");
  350. ELSIF res = Disks.DeviceInUse THEN out.String("DeviceInUse");
  351. ELSIF res = Disks.MediaMissing THEN out.String("MediaMissing");
  352. ELSE out.String("Unknown (res: "); out.Int(res, 0); out.String(")");
  353. END;
  354. END ShowDiskres;
  355. END DiskTests.
  356. DiskTests.WriteTestData USB0#1 ~ SystemTools.Free DiskTests ~
  357. DiskTests.VerifyTestData USB0#1 ~
  358. DiskTests.Test USB0#1 ~
  359. DiskTests.TransferBlocks USB0#1 READ 0 6 ~
  360. UsbInfo.TraceOn Custom~
  361. UsbInfo.TraceNone ~
  362. Partitions.ShowOps ~
  363. Partitions.ShowOps detail ~
  364. Partitions.Abort 1 ~