TestSuite.Mod 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. MODULE TestSuite; (** AUTHOR "negelef"; PURPOSE "Simple testing framework"; *)
  2. IMPORT Streams, Files, Commands, Strings, TextUtilities, Diagnostics;
  3. CONST
  4. PositiveTest = 0;
  5. NegativeTest = 1;
  6. Positive* = 0;
  7. Negative* = 1;
  8. Failure* = 2;
  9. TYPE
  10. TestType* = INTEGER;
  11. TestName = ARRAY 100 OF CHAR;
  12. TestResult* = POINTER TO RECORD
  13. type-: TestType;
  14. name-: TestName;
  15. succeeded-, new-: BOOLEAN;
  16. next: TestResult
  17. END;
  18. TestResultList = RECORD
  19. first, last: TestResult;
  20. END;
  21. Report* = OBJECT
  22. VAR tests-, succeeded-, succeededThisTime-, failed-, failedThisTime-: INTEGER;
  23. PROCEDURE Open*;
  24. END Open;
  25. PROCEDURE Handle* (result: TestResult);
  26. END Handle;
  27. PROCEDURE Close*;
  28. END Close;
  29. END Report;
  30. Tester* = OBJECT
  31. VAR
  32. tests, results: TestResultList;
  33. diagnostics-: Diagnostics.Diagnostics;
  34. PROCEDURE &Init* (diagnostics: Diagnostics.Diagnostics);
  35. BEGIN SELF.diagnostics := diagnostics;
  36. END Init;
  37. PROCEDURE Process* (r: Streams.Reader);
  38. VAR type: TestType; name: TestName; line: ARRAY 200 OF CHAR;
  39. code: Strings.Buffer; writer : Streams.Writer;
  40. string : Strings.String; reader: Streams.StringReader;
  41. BEGIN
  42. NEW (code, 1000); writer := code.GetWriter ();
  43. ClearList (tests);
  44. WHILE SkipComment (r) DO
  45. IF ~ReadType (r, type) OR ~SkipWhitespace (r) OR ~ReadText (r, name) THEN
  46. diagnostics.Error (name, r.Pos(), Diagnostics.Invalid, "parse error"); RETURN;
  47. END;
  48. IF FindResult (tests, name) # NIL THEN
  49. diagnostics.Error (name, Diagnostics.Invalid, Diagnostics.Invalid, "duplicated test"); RETURN;
  50. END;
  51. code.Clear; writer.Reset;
  52. WHILE SkipLn (r) & Tabulator (r) & ReadText (r, line) DO writer.Char (09X); writer.String (line); writer.Char (0AX); END;
  53. string := code.GetString ();
  54. NEW (reader, code.GetLength ());
  55. reader.Set (string^);
  56. AddResult (tests, type, name, Handle (reader, r.Pos () - writer.Pos () - 1, name, type) = type);
  57. END;
  58. END Process;
  59. PROCEDURE Handle* (r: Streams.Reader; pos: LONGINT; CONST name: ARRAY OF CHAR; type: TestType): INTEGER;
  60. END Handle;
  61. PROCEDURE Print* (report: Report);
  62. VAR test, result: TestResult;
  63. BEGIN
  64. report.tests := 0; report.succeeded := 0; report.succeededThisTime := 0; report.failed := 0; report.failedThisTime := 0;
  65. report.Open;
  66. test := tests.first;
  67. WHILE test # NIL DO
  68. INC (report.tests); IF test.succeeded THEN INC (report.succeeded) ELSE INC (report.failed) END;
  69. result := FindResult (results, test.name);
  70. test.new := (result = NIL) OR (test.succeeded # result.succeeded);
  71. IF test.new THEN IF test.succeeded THEN INC (report.succeededThisTime) ELSE INC (report.failedThisTime) END END;
  72. IF (~test.succeeded) OR (test.new) THEN report.Handle (test) END;
  73. test := test.next;
  74. END;
  75. report.Close;
  76. END Print;
  77. END Tester;
  78. StreamReport* = OBJECT (Report)
  79. VAR w: Streams.Writer; tw: TextUtilities.TextWriter;
  80. PROCEDURE &InitStreamReport *(w: Streams.Writer);
  81. BEGIN SELF.w := w; IF w IS TextUtilities.TextWriter THEN tw := w(TextUtilities.TextWriter) ELSE tw := NIL END;
  82. END InitStreamReport;
  83. PROCEDURE Open;
  84. BEGIN w.Ln; Bold; w.String ("Test results:"); Default; w.Ln
  85. END Open;
  86. PROCEDURE Green;
  87. BEGIN IF tw # NIL THEN tw.SetFontColor (000C000FFH); tw.SetFontStyle ({0}) END;
  88. END Green;
  89. PROCEDURE Red;
  90. BEGIN IF tw # NIL THEN tw.SetFontColor (LONGINT(0FF0000FFH)); tw.SetFontStyle ({0}) END;
  91. END Red;
  92. PROCEDURE Orange;
  93. BEGIN IF tw # NIL THEN tw.SetFontColor (LONGINT (0FFC000FFH)); tw.SetFontStyle ({0}) END;
  94. END Orange;
  95. PROCEDURE Default;
  96. BEGIN IF tw # NIL THEN tw.SetFontColor (0000000FFH); tw.SetFontStyle ({}) END;
  97. END Default;
  98. PROCEDURE Bold;
  99. BEGIN IF tw # NIL THEN tw.SetFontStyle ({0}) END;
  100. END Bold;
  101. PROCEDURE Handle (test: TestResult);
  102. BEGIN
  103. IF test.type = PositiveTest THEN w.String ("positive: ");
  104. ELSIF test.type = NegativeTest THEN w.String ("negative: ") END;
  105. w.String (test.name); w.String (": ");
  106. IF test.succeeded THEN
  107. Green;
  108. w.String ("succeeded")
  109. ELSE
  110. IF test.new THEN Orange ELSE Red END;
  111. w.String ("failed")
  112. END;
  113. Default; w.Ln
  114. END Handle;
  115. PROCEDURE Close;
  116. BEGIN w.Ln; Bold; w.String ("Summary:"); Default; w.Ln;
  117. w.String ("number of tests:"); w.Char (9X); w.Int (tests, 0); w.Ln;
  118. w.String ("successful tests:"); w.Char (9X); IF succeeded = tests THEN Green ELSE Red END; w.Int (succeeded, 0); Default;
  119. IF succeededThisTime > 0 THEN w.Char (9X); w.Char ('('); w.Char ('+'); w.Int (succeededThisTime, 0); w.Char (')'); END; w.Ln;
  120. w.String ("failed tests:"); w.Char (9X); w.Char (9X); IF failed = 0 THEN Green ELSE Red END; w.Int (failed, 0); Default;
  121. IF failedThisTime > 0 THEN w.Char (9X); w.Char ('('); w.Char ('+'); w.Int (failedThisTime, 0); w.Char (')'); END; w.Ln;
  122. END Close;
  123. END StreamReport;
  124. (* helper procedures for parsing *)
  125. PROCEDURE SkipComment (r: Streams.Reader): BOOLEAN;
  126. VAR char: CHAR;
  127. BEGIN char := r.Peek (); WHILE (char = '#') OR (char = 0AX) OR (char = 0DX) DO r.SkipLn; char := r.Peek (); END; RETURN (r.res = Streams.Ok) & (char # 0X);
  128. END SkipComment;
  129. PROCEDURE SkipWhitespace (r: Streams.Reader): BOOLEAN;
  130. BEGIN WHILE r.Peek () = ' ' DO r.SkipBytes (1) END; RETURN r.res = Streams.Ok
  131. END SkipWhitespace;
  132. PROCEDURE SkipLn (r: Streams.Reader): BOOLEAN;
  133. BEGIN WHILE (r.Peek () = 0AX) OR (r.Peek () = 0DX) DO r.SkipBytes (1) END; RETURN r.res = Streams.Ok
  134. END SkipLn;
  135. PROCEDURE ReadType (r: Streams.Reader; VAR type: TestType): BOOLEAN;
  136. VAR c: CHAR; string: ARRAY 10 OF CHAR; i: INTEGER;
  137. BEGIN
  138. i := 0; r.Char (c);
  139. WHILE (c # ':') & (i # LEN (string)) DO string[i] := c; INC (i); r.Char (c) END;
  140. IF i = LEN (string) THEN RETURN FALSE END;
  141. string[i] := 0X;
  142. IF string = "positive" THEN type := PositiveTest; RETURN TRUE
  143. ELSIF string = "negative" THEN type := NegativeTest; RETURN TRUE
  144. ELSE RETURN FALSE END
  145. END ReadType;
  146. PROCEDURE ReadText (r: Streams.Reader; VAR text: ARRAY OF CHAR): BOOLEAN;
  147. BEGIN r.Ln (text); RETURN r.res = Streams.Ok
  148. END ReadText;
  149. PROCEDURE Tabulator (r: Streams.Reader): BOOLEAN;
  150. BEGIN RETURN (r.Peek () = 09X) & (r.Get () = 09X)
  151. END Tabulator;
  152. PROCEDURE ReadBoolean (r: Streams.Reader; VAR boolean: BOOLEAN): BOOLEAN;
  153. VAR value: LONGINT;
  154. BEGIN r.Int (value, FALSE); boolean := value = 1; RETURN r.res = Streams.Ok
  155. END ReadBoolean;
  156. PROCEDURE ReadResults (r: Streams.Reader; VAR list: TestResultList);
  157. VAR succeeded: BOOLEAN; name: TestName;
  158. BEGIN WHILE ReadBoolean (r, succeeded) & SkipWhitespace (r) & ReadText (r, name) DO AddResult (list, 0, name, succeeded) END
  159. END ReadResults;
  160. PROCEDURE WriteResults (w: Streams.Writer; CONST list: TestResultList);
  161. VAR result: TestResult;
  162. BEGIN result := list.first;
  163. WHILE result # NIL DO
  164. IF result.succeeded THEN w.Char ('1') ELSE w.Char ('0') END;
  165. w.Char (' '); w.String (result.name); w.Ln;
  166. result := result.next
  167. END
  168. END WriteResults;
  169. (* test results management *)
  170. PROCEDURE ClearList (VAR list: TestResultList);
  171. BEGIN list.first := NIL; list.last := NIL
  172. END ClearList;
  173. PROCEDURE AddResult (VAR list: TestResultList; type: TestType; CONST name: ARRAY OF CHAR; succeeded: BOOLEAN);
  174. VAR result: TestResult;
  175. BEGIN NEW (result); COPY (name, result.name); result.succeeded := succeeded; result.new := FALSE; result.next := NIL; result.type := type;
  176. IF list.first = NIL THEN list.first := result ELSE list.last.next := result END; list.last := result;
  177. END AddResult;
  178. PROCEDURE FindResult (CONST list: TestResultList; CONST name: ARRAY OF CHAR): TestResult;
  179. VAR result: TestResult;
  180. BEGIN result := list.first; WHILE (result # NIL) & (result.name # name) DO result := result.next END; RETURN result
  181. END FindResult;
  182. (* public interface helper *)
  183. PROCEDURE DriveByReader* (reader: Streams.Reader; error: Streams.Writer; CONST resultname: ARRAY OF CHAR; tester: Tester);
  184. VAR resreader: Files.Reader;result: Files.File; writer: Files.Writer;
  185. BEGIN
  186. IF reader = NIL THEN
  187. RETURN;
  188. END;
  189. ClearList (tester.results);
  190. IF resultname # "" THEN
  191. result := Files.Old (resultname);
  192. IF result # NIL THEN
  193. NEW (resreader, result, 0); ReadResults (resreader, tester.results)
  194. END
  195. END;
  196. tester.Process (reader);
  197. IF resultname # "" THEN
  198. result := Files.New (resultname);
  199. IF result = NIL THEN
  200. error.String ("Failed to open result file "); error.String (resultname); error.Ln;
  201. RETURN;
  202. ELSE
  203. NEW (writer, result, 0); WriteResults (writer, tester.tests); writer.Update; Files.Register (result);
  204. END
  205. END;
  206. END DriveByReader;
  207. (* public interface helper *)
  208. PROCEDURE Drive* (context: Commands.Context; tester: Tester);
  209. VAR testname, resultname: Files.FileName; test: Files.File; reader: Files.Reader;
  210. BEGIN
  211. IF context.arg.GetString (testname) THEN
  212. test := Files.Old (testname);
  213. IF test = NIL THEN
  214. context.error.String ("Failed to open test file "); context.error.String (testname); context.error.Ln;
  215. RETURN;
  216. END;
  217. ELSE
  218. context.result := Commands.CommandParseError;
  219. END;
  220. NEW (reader, test, 0);
  221. IF ~context.arg.GetString (resultname) THEN
  222. resultname := "";
  223. END;
  224. DriveByReader(reader, context.error, resultname, tester);
  225. END Drive;
  226. END TestSuite.