TestSuite.Mod 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  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): BOOLEAN;
  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(), "parse error"); RETURN FALSE;
  47. END;
  48. IF FindResult (tests, name) # NIL THEN
  49. diagnostics.Error (name, Streams.Invalid, "duplicated test"); RETURN FALSE;
  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. RETURN TRUE;
  59. END Process;
  60. PROCEDURE Handle* (r: Streams.Reader; pos: LONGINT; CONST name: ARRAY OF CHAR; type: TestType): INTEGER;
  61. END Handle;
  62. PROCEDURE Print* (report: Report);
  63. VAR test, result: TestResult;
  64. BEGIN
  65. report.tests := 0; report.succeeded := 0; report.succeededThisTime := 0; report.failed := 0; report.failedThisTime := 0;
  66. report.Open;
  67. test := tests.first;
  68. WHILE test # NIL DO
  69. INC (report.tests); IF test.succeeded THEN INC (report.succeeded) ELSE INC (report.failed) END;
  70. result := FindResult (results, test.name);
  71. test.new := (result = NIL) OR (test.succeeded # result.succeeded);
  72. IF test.new THEN IF test.succeeded THEN INC (report.succeededThisTime) ELSE INC (report.failedThisTime) END END;
  73. IF (~test.succeeded) OR (test.new) THEN report.Handle (test) END;
  74. test := test.next;
  75. END;
  76. report.Close;
  77. END Print;
  78. END Tester;
  79. StreamReport* = OBJECT (Report)
  80. VAR w: Streams.Writer; tw: TextUtilities.TextWriter;
  81. PROCEDURE &InitStreamReport *(w: Streams.Writer);
  82. BEGIN SELF.w := w; IF w IS TextUtilities.TextWriter THEN tw := w(TextUtilities.TextWriter) ELSE tw := NIL END;
  83. END InitStreamReport;
  84. PROCEDURE Open*;
  85. BEGIN w.Ln; Bold; w.String ("Test results:"); Default; w.Ln
  86. END Open;
  87. PROCEDURE Green;
  88. BEGIN IF tw # NIL THEN tw.SetFontColor (000C000FFH); tw.SetFontStyle ({0}) END;
  89. END Green;
  90. PROCEDURE Red;
  91. BEGIN IF tw # NIL THEN tw.SetFontColor (LONGINT(0FF0000FFH)); tw.SetFontStyle ({0}) END;
  92. END Red;
  93. PROCEDURE Orange;
  94. BEGIN IF tw # NIL THEN tw.SetFontColor (LONGINT (0FFC000FFH)); tw.SetFontStyle ({0}) END;
  95. END Orange;
  96. PROCEDURE Default;
  97. BEGIN IF tw # NIL THEN tw.SetFontColor (0000000FFH); tw.SetFontStyle ({}) END;
  98. END Default;
  99. PROCEDURE Bold;
  100. BEGIN IF tw # NIL THEN tw.SetFontStyle ({0}) END;
  101. END Bold;
  102. PROCEDURE Handle* (test: TestResult);
  103. BEGIN
  104. IF test.type = PositiveTest THEN w.String ("positive: ");
  105. ELSIF test.type = NegativeTest THEN w.String ("negative: ") END;
  106. w.String (test.name); w.String (": ");
  107. IF test.succeeded THEN
  108. Green;
  109. w.String ("succeeded")
  110. ELSE
  111. IF test.new THEN Orange ELSE Red END;
  112. w.String ("failed")
  113. END;
  114. Default; w.Ln
  115. END Handle;
  116. PROCEDURE Close*;
  117. BEGIN w.Ln; Bold; w.String ("Summary:"); Default; w.Ln;
  118. w.String ("number of tests:"); w.Char (9X); w.Int (tests, 0); w.Ln;
  119. w.String ("successful tests:"); w.Char (9X); IF succeeded = tests THEN Green ELSE Red END; w.Int (succeeded, 0); Default;
  120. IF succeededThisTime > 0 THEN w.Char (9X); w.Char ('('); w.Char ('+'); w.Int (succeededThisTime, 0); w.Char (')'); END; w.Ln;
  121. w.String ("failed tests:"); w.Char (9X); w.Char (9X); IF failed = 0 THEN Green ELSE Red END; w.Int (failed, 0); Default;
  122. IF failedThisTime > 0 THEN w.Char (9X); w.Char ('('); w.Char ('+'); w.Int (failedThisTime, 0); w.Char (')'); END; w.Ln;
  123. END Close;
  124. END StreamReport;
  125. (* helper procedures for parsing *)
  126. PROCEDURE SkipComment (r: Streams.Reader): BOOLEAN;
  127. VAR char: CHAR;
  128. 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);
  129. END SkipComment;
  130. PROCEDURE SkipWhitespace (r: Streams.Reader): BOOLEAN;
  131. BEGIN WHILE r.Peek () = ' ' DO r.SkipBytes (1) END; RETURN r.res = Streams.Ok
  132. END SkipWhitespace;
  133. PROCEDURE SkipLn (r: Streams.Reader): BOOLEAN;
  134. BEGIN WHILE (r.Peek () = 0AX) OR (r.Peek () = 0DX) DO r.SkipBytes (1) END; RETURN r.res = Streams.Ok
  135. END SkipLn;
  136. PROCEDURE ReadType (r: Streams.Reader; VAR type: TestType): BOOLEAN;
  137. VAR c: CHAR; string: ARRAY 10 OF CHAR; i: INTEGER;
  138. BEGIN
  139. i := 0; r.Char (c);
  140. WHILE (c # ':') & (i # LEN (string)) DO string[i] := c; INC (i); r.Char (c) END;
  141. IF i = LEN (string) THEN RETURN FALSE END;
  142. string[i] := 0X;
  143. IF string = "positive" THEN type := PositiveTest; RETURN TRUE
  144. ELSIF string = "negative" THEN type := NegativeTest; RETURN TRUE
  145. ELSE RETURN FALSE END
  146. END ReadType;
  147. PROCEDURE ReadText (r: Streams.Reader; VAR text: ARRAY OF CHAR): BOOLEAN;
  148. BEGIN r.Ln (text); RETURN r.res = Streams.Ok
  149. END ReadText;
  150. PROCEDURE Tabulator (r: Streams.Reader): BOOLEAN;
  151. BEGIN RETURN (r.Peek () = 09X) & (r.Get () = 09X)
  152. END Tabulator;
  153. PROCEDURE ReadBoolean (r: Streams.Reader; VAR boolean: BOOLEAN): BOOLEAN;
  154. VAR value: LONGINT;
  155. BEGIN r.Int (value, FALSE); boolean := value = 1; RETURN r.res = Streams.Ok
  156. END ReadBoolean;
  157. PROCEDURE ReadResults (r: Streams.Reader; VAR list: TestResultList);
  158. VAR succeeded: BOOLEAN; name: TestName;
  159. BEGIN WHILE ReadBoolean (r, succeeded) & SkipWhitespace (r) & ReadText (r, name) DO AddResult (list, 0, name, succeeded) END
  160. END ReadResults;
  161. PROCEDURE WriteResults (w: Streams.Writer; CONST list: TestResultList);
  162. VAR result: TestResult;
  163. BEGIN result := list.first;
  164. WHILE result # NIL DO
  165. IF result.succeeded THEN w.Char ('1') ELSE w.Char ('0') END;
  166. w.Char (' '); w.String (result.name); w.Ln;
  167. result := result.next
  168. END
  169. END WriteResults;
  170. (* test results management *)
  171. PROCEDURE ClearList (VAR list: TestResultList);
  172. BEGIN list.first := NIL; list.last := NIL
  173. END ClearList;
  174. PROCEDURE AddResult (VAR list: TestResultList; type: TestType; CONST name: ARRAY OF CHAR; succeeded: BOOLEAN);
  175. VAR result: TestResult;
  176. BEGIN NEW (result); COPY (name, result.name); result.succeeded := succeeded; result.new := FALSE; result.next := NIL; result.type := type;
  177. IF list.first = NIL THEN list.first := result ELSE list.last.next := result END; list.last := result;
  178. END AddResult;
  179. PROCEDURE FindResult (CONST list: TestResultList; CONST name: ARRAY OF CHAR): TestResult;
  180. VAR result: TestResult;
  181. BEGIN result := list.first; WHILE (result # NIL) & (result.name # name) DO result := result.next END; RETURN result
  182. END FindResult;
  183. (* public interface helper *)
  184. PROCEDURE DriveByReader* (reader: Streams.Reader; error: Streams.Writer; CONST resultname: ARRAY OF CHAR; tester: Tester): BOOLEAN;
  185. VAR resreader: Files.Reader;result: Files.File; writer: Files.Writer;
  186. BEGIN
  187. IF reader = NIL THEN
  188. RETURN TRUE;
  189. END;
  190. ClearList (tester.results);
  191. IF resultname # "" THEN
  192. result := Files.Old (resultname);
  193. IF result # NIL THEN
  194. NEW (resreader, result, 0); ReadResults (resreader, tester.results)
  195. END
  196. END;
  197. IF ~tester.Process (reader) THEN RETURN FALSE END;
  198. IF resultname # "" THEN
  199. result := Files.New (resultname);
  200. IF result = NIL THEN
  201. error.String ("Failed to open result file "); error.String (resultname); error.Ln;
  202. RETURN FALSE;
  203. ELSE
  204. NEW (writer, result, 0); WriteResults (writer, tester.tests); writer.Update; Files.Register (result);
  205. END
  206. END;
  207. RETURN TRUE;
  208. END DriveByReader;
  209. (* public interface helper *)
  210. PROCEDURE Drive* (context: Commands.Context; tester: Tester);
  211. VAR testname, resultname: Files.FileName; test: Files.File; reader: Files.Reader;
  212. BEGIN
  213. IF context.arg.GetString (testname) THEN
  214. test := Files.Old (testname);
  215. IF test = NIL THEN
  216. context.error.String ("Failed to open test file "); context.error.String (testname); context.error.Ln;
  217. context.result := Commands.CommandError;
  218. RETURN;
  219. END;
  220. ELSE
  221. context.result := Commands.CommandParseError;
  222. END;
  223. NEW (reader, test, 0);
  224. IF ~context.arg.GetString (resultname) THEN
  225. resultname := "";
  226. END;
  227. IF DriveByReader(reader, context.error, resultname, tester) THEN
  228. context.result := Commands.CommandError;
  229. END;
  230. END Drive;
  231. END TestSuite.