Source File testing/testing.ils
1 2
3 4
5 6
7 8 9 10 11 12 13 14
15 16
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
35 36
37 38 39 40 41 (defclass VedaTestingContext ()
42 ((name @initarg name @reader GetName)
43 (packagePath @initarg packagePath @reader GetPackagePath)
44 (output @initform nil @reader GetOutput @writer SetOutput)
45 (failed @initform nil @reader GetFailed @writer SetFailed)
46 (start @initform nil @reader GetStart @writer SetStart)
47 (duration @initform nil @reader GetDuration @writer SetDuration)))
48
49 50 51 52
53 54 55 56 57 (defclass VedaTestingPackageAbstractInfo ()
58 ( 59 60 (name
61 @initarg name
62 @reader GetName)
63 64 (description
65 @initarg description
66 @reader GetDescription)
67 68 69 70 71 (authors
72 @initarg authors
73 @reader GetAuthors)))
74
75 76 77 78 79 (defclass VedaTestingPackageFilesystemInfo (VedaTestingPackageAbstractInfo)
80 ( 81 82 (base
83 @initarg base
84 @reader GetBaseDir)
85 86 (header
87 @initarg header
88 @reader GetHeaderFilename)
89 90 91 92 93 (requires
94 @initarg requires
95 @reader GetPackageRequires)
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 (sources
111 @initarg sources
112 @reader GetSourceFilenames)
113 114 (tests
115 @initarg tests
116 @reader GetTestFilenames)))
117
118 119 120 121 122 (defmethod printObject ((pi VedaTestingPackageFilesystemInfo)
123 @optional (port poport))
124 (fprintf port "#<%s %L; base: %L>"
125 (className (classOf pi)) pi->name pi->base))
126
127 128 129 130 131 (defclass VedaTestingPackageSearchOptions ()
132 ( 133 134 135 136 137 (searchPath
138 @initarg searchPath
139 @initform '("pkg/" "../")
140 @reader GetSearchPath
141 @writer SetSearchPath)))
142
143 144 145 146 147 (defclass VedaTestingPackageLoadOptions (VedaTestingPackageSearchOptions)
148 ( 149 150 151 152 153 (requires
154 @initarg requires
155 @initform t
156 @reader GetRequires
157 @writer SetRequires)
158 159 160 161 162 163 164 (forceReload
165 @initarg forceReload
166 @initform nil
167 @reader GetForceReload
168 @writer SetForceReload)))
169
170 171 (let ()
172 173 174 (defun Export (suffix fn)
175 (putd (concat 'VedaTesting suffix) fn))
176
177 178 (defmethod VedaTestingFail ((context VedaTestingContext))
179 (SetFailed context t))
180
181 182 183 (defmethod VedaTestingFailNow ((context VedaTestingContext))
184 (VedaTestingFail context)
185 (err nil))
186
187 188 (defun Log (context s)
189 (let ((lines (parseString s "\n"))
190 (output (GetOutput context)))
191 (foreach line lines
192 (push line output))
193 (SetOutput context output)))
194
195 196 197 198 199 (defun Format (format @rest args)
200 (cond
201 202 ((isCallable 'XtsSprintf)
203 (apply XtsSprintf format args))
204 (t
205 (let ((code (nconc (list 'sprintf nil format)
206 (foreach mapcar arg args
207 (list 'quote arg)))))
208 (eval code)))))
209
210 211 212 (defmethod VedaTestingLog ((context VedaTestingContext) @rest args)
213 (let ((pieces nil))
214 (foreach arg args
215 (when pieces
216 (push " " pieces))
217 (cond
218 ((stringp arg)
219 (push arg pieces))
220 (t
221 (push (sprintf nil "%L" arg) pieces))))
222 (Log context (buildString (reverse pieces) ""))))
223
224 225 226 (defmethod VedaTestingLogf ((context VedaTestingContext) format @rest args)
227 (Log context (apply Format format args)))
228
229 230 (defmethod VedaTestingError ((context VedaTestingContext) @rest args)
231 (apply VedaTestingLog context args)
232 (VedaTestingFail context))
233
234 235 (defmethod VedaTestingErrorf ((context VedaTestingContext) format @rest args)
236 (apply VedaTestingLogf context format args)
237 (VedaTestingFail context))
238
239 240 (defmethod VedaTestingFatal ((context VedaTestingContext) @rest args)
241 (apply VedaTestingLog context args)
242 (VedaTestingFailNow context))
243
244 245 (defmethod VedaTestingFatalf ((context VedaTestingContext) format @rest args)
246 (apply VedaTestingLogf context format args)
247 (VedaTestingFailNow context))
248
249 250 (defun TRunner (context f traceOnErrorP)
251 (SetStart context (getCurrentTime))
252 (let ((r (errset (funcall f context) traceOnErrorP)))
253 (unless r
254 (SetFailed context t)))
255 (SetDuration context
256 (compareTime (GetStart context)
257 (getCurrentTime))))
258
259 260 (defun BuildOutput (context)
261 (if (GetOutput context)
262 (strcat "\t" (buildString (reverse (GetOutput context)) "\n\t") "\n")
263 ""))
264
265 266 267 (defun TextReporter (options)
268 (lambda (event @rest args)
269 (case event
270 (startTest
271 (let ((context (car args)))
272 (when options->chattyp
273 (printf "=== RUN %s\n" (GetName context)))))
274 (endTest
275 (let ((context (car args)))
276 (when (or (GetFailed context) options->chattyp)
277 (let ((tstr (if (plusp (GetDuration context))
278 (sprintf nil " (%d seconds)"
279 (GetDuration context))
280 ""))
281 (head (if (GetFailed context) "FAIL" "PASS"))
282 (format "--- %s: %s%s\n%s"))
283 (printf format head (GetName context)
284 tstr (BuildOutput context)))))))))
285
286 (defun GenTimestamp ()
287 288 "2012-09-25T18:48:46")
289
290 291 (define xmlMustEscapes
292 '(("&" "&")
293 ("<" "<")))
294
295 296 297 (define xmlAposEscapes
298 (append
299 '(("'" "&apos"))
300 xmlMustEscapes))
301
302 303 (defun XmlEscape (string escapes)
304 (cond
305 ((exists entry escapes
306 (nindex string (car entry)))
307 (letseq ((parts (parseString string ""))
308 (safe (foreach mapcar part parts
309 (let ((it (assoc part escapes)))
310 (if it (cadr it) part)))))
311 (buildString safe "")))
312 (t
313 string)))
314
315 316 317 318 (defun XmlEscaper (escapes)
319 (lambda (string)
320 (XmlEscape string escapes)))
321
322 323 324 325 (defun WriteXml (stream contexts pkg)
326 (let ((EA (XmlEscaper xmlAposEscapes))
327 (EB (XmlEscaper xmlMustEscapes))
328 (pkg (or pkg "<None>"))
329 (timestamp (GenTimestamp))
330 (testCount 0)
331 (failureCount 0)
332 (totalTime 0)
333 (otherOutputs nil))
334 (foreach context contexts
335 (setq testCount (testCount + 1))
336 (when (GetFailed context)
337 (setq failureCount (failureCount + 1)))
338 (setq totalTime (totalTime + (GetDuration context))))
339 (fprintf stream "<?xml version='1.0' encoding='ISO-8859-1'?>\n")
340 (fprintf stream "<testsuites>\n")
341 (fprintf stream " <testsuite package='%s' id='0' name='Unknown'\n"
342 (EA pkg))
343 (fprintf stream " timestamp='%s' hostname='localhost'\n"
344 (EA timestamp))
345 (fprintf stream " tests='%d' failures='%d' errors='0'\n"
346 testCount failureCount)
347 (fprintf stream " time='%d'>\n"
348 totalTime)
349 (fprintf stream " <properties/>\n")
350 (foreach context contexts
351 (fprintf stream " <testcase name='%s' classname='t' time='%d'>\n"
352 (EA (GetName context))
353 (GetDuration context))
354 (let ((lines (reverse (GetOutput context))))
355 (cond
356 ((GetFailed context)
357 (let ((messageMea (when lines
358 (sprintf nil " message='%s'"
359 (EA (car lines))))))
360 (fprintf stream " <failure type='Fail'%s>"
361 messageMea)
362 (foreach line lines
363 (fprintf stream "%s\n" (EB line)))
364 (fprintf stream "</failure>\n")))
365 (t
366 (push lines otherOutputs))))
367 (fprintf stream " </testcase>\n"))
368 (cond
369 ((null otherOutputs)
370 (fprintf stream " <system-out/>\n"))
371 (t
372 (fprintf stream " <system-out>")
373 (foreach lines (reverse otherOutputs)
374 (foreach line lines
375 (fprintf stream "%s\n" (EB line))))
376 (fprintf stream "</system-out>\n")))
377 (fprintf stream " <system-err/>\n")
378 (fprintf stream " </testsuite>\n")
379 (fprintf stream "</testsuites>\n")))
380
381 382 383 (defun XmlReporter (options)
384 (let ((filename (or options->reportFilename
385 "./result.xml"))
386 (contexts nil))
387 (lambda (event @rest args)
388 (case event
389 (startTest
390 (let ((context (car args)))
391 (when options->chattyp
392 (printf "=== RUN %s\n" (GetName context)))))
393 (endTest
394 (let ((context (car args)))
395 (push context contexts)))
396 (endTestsuite
397 (when contexts
398 (let ((stream (or (outfile filename)
399 (error "Cannot open %L for writing." filename))))
400 (WriteXml stream (reverse contexts) options->package)
401 (close stream))))))))
402
403 404 405 (defun FinalizeOptions (options)
406 (let ((newOptions (if options (copy options) (list nil))))
407 (newOptions->report = (case options->reportFormat
408 ((nil text) (TextReporter options))
409 (xml (XmlReporter options))
410 411 412 413 (void (lambda (@rest _args)))
414 (t (error "Unsupported report format %L"
415 options->reportFormat))))
416 newOptions))
417
418 419 420 (defun RunTests (tests @key options)
421 (let ((options (FinalizeOptions options))
422 (ok tests))
423 (let ((report options->report)
424 (traceOnErrorP (neq options->reportFormat 'void)))
425 (funcall report 'startTestsuite)
426 (foreach test tests
427 (letseq ((name (strcat test))
428 (context (makeInstance 'VedaTestingContext
429 ?name name
430 ?packagePath options->packagePath)))
431 (funcall report 'startTest context)
432 (TRunner context test traceOnErrorP)
433 (funcall report 'endTest context)
434 (setq ok (and ok (not (GetFailed context))))))
435 (funcall report 'endTestsuite ok))
436 ok))
437
438 (Export 'RunTests RunTests)
439
440 441 442 (defun FindTestMethods ()
443 (let ((syms (rexMatchList "^Test_" oblist)))
444 (setof sym syms
445 (and (isCallable sym)
446 447 448 t))))
449
450 451 452 (defun MaskExistingTests ()
453 (let ((pre (makeTable 'pre nil))
454 (old (FindTestMethods)))
455 (foreach sym old
456 (setarray pre sym (getd sym))
457 (putd sym nil))
458 pre))
459
460 461 462 463 (defun RestoreAndFindNewTests (pre)
464 (let ((now (FindTestMethods)))
465 (foreach sym now
466 (remove sym pre))
467 (foreach sym pre
468 469 (putd sym (arrayref pre sym)))
470 now))
471
472 473 474 475 476 477 478 479 480 (defun LoadTestsUsingLoader (loader)
481 (let ((pre (MaskExistingTests)))
482 (funcall loader ?subset 'tests)
483 (RestoreAndFindNewTests pre)))
484
485 486 487 488 489 490 491 (defun RunTestsFromFiles (files @key options)
492 (let ((tests (LoadTestsUsingLoader (FilesLoader files))))
493 (RunTests tests ?options options)))
494
495 (Export 'RunTestsFromFiles RunTestsFromFiles)
496
497 498 (define defaultOptions
499 (makeInstance 'VedaTestingPackageLoadOptions))
500
501 502 503 504 (defun FilesLoader (filenames @key dir)
505 (lambda (@rest _ignored)
506 (foreach filename filenames
507 (when dir
508 (setq filename (strcat dir filename)))
509 (load filename))
510 t))
511
512 513 514 515 516 517 (defun Trim (characterBag string @key fromEndP)
518 (let ((a 1)
519 (n (strlen string)))
520 (let ((b n)
521 (i (if fromEndP n a)))
522 (while (and (a <= b)
523 (nindex characterBag (getchar string i)))
524 (setq i (if fromEndP
525 (setq b (b - 1))
526 (setq a (a + 1)))))
527 (cond
528 ((and (onep a) (equal b n))
529 string)
530 (t
531 (substring string a (b - a + 1)))))))
532
533 534 535 (define lineCommentTrimBag
536 "; \t")
537
538 539 540 (define blockCommentTrimBag
541 "* \t")
542
543 544 545 546 547 548 549 550 551 552 553 554 555 (defun ParseDescriptionLine (line filename)
556 557 (letseq ((pieces (parseString line " \t\n"))
558 (bag (cond
559 ((equal (car pieces) ";;;") lineCommentTrimBag)
560 ((equal (car pieces) "/*") blockCommentTrimBag))))
561 (when (and bag
562 (equal (cadr pieces) filename)
563 (equal (caddr pieces) "---"))
564 (list bag (buildString (nthcdr 3 pieces))))))
565
566 567 568 (defun PrefixP (needle haystack)
569 (let ((n (strlen needle)))
570 (when (n <= (strlen haystack))
571 (let ((i 1))
572 (while (and i (i <= n))
573 (setq i (when (eq (getchar needle i) (getchar haystack i))
574 (i + 1))))
575 i))))
576
577 578 579 580 (defun SuffixP (needle haystack)
581 (let ((n (strlen needle))
582 (m (strlen haystack)))
583 (when (n <= m)
584 (let ((i 1)
585 (offset (m - n)))
586 (while (and i (i <= n))
587 (setq i (when (eq (getchar needle i)
588 (getchar haystack (offset + i)))
589 (i + 1))))
590 (when i
591 offset)))))
592
593 594 595 596 (defun TrimMark (trimBag line mark)
597 (letseq ((trimmed (Trim trimBag line))
598 (i (PrefixP mark trimmed)))
599 (when i
600 (substring trimmed i))))
601
602 603 604 605 606 607 (defun ParseHeader (path filename)
608 (let ((port (infile path)))
609 (when port
610 (let ((info (list nil 'header filename))
611 (authors nil)
612 (line nil)
613 (desc nil)
614 (it nil)
615 (donep nil))
616 (while (and (not donep) (gets line port))
617 (cond
618 ((equal line "\n")
619 620 )
621 ((not (or desc (setq desc (ParseDescriptionLine line filename))))
622 (setq info nil)
623 (setq donep nil))
624 625 ((setq it (TrimMark (car desc) line "@load"))
626 (info->load = (parseString it ", \t\n")))
627 ((setq it (TrimMark (car desc) line "@requires"))
628 (info->requires = (parseString it ", \t\n")))
629 ((setq it (TrimMark (car desc) line "@author"))
630 (letseq ((s1 (Trim " \t\n" it))
631 (s2 (Trim " \t\n" s1 ?fromEndP t)))
632 (push s2 authors)))
633 634 635 ((or (TrimMark (car desc) line "Commentary:")
636 (TrimMark (car desc) line "Code:")
637 (when (eq (car desc) blockCommentTrimBag)
638 (nindex line "*/")))
639 (setq donep t))))
640 (close port)
641 (when info
642 (info->description = (cadr desc))
643 (info->authors = (reverse authors))
644 info)))))
645
646 647 648 (define ellipsis
649 "...")
650
651 652 653 654 655 (defun ProcessLoadDirective (directive files)
656 (let ((files (copy files))
657 (ellipsisp nil))
658 (let ((loads (foreach maplist dtail directive
659 (let ((file (car dtail)))
660 (cond
661 ((let ((ftail (member file files)))
662 (when ftail
663 (rplaca ftail nil)
664 file)))
665 ((and (equal file ellipsis)
666 (null (cdr dtail)))
667 (setq ellipsisp t)
668 nil)
669 (t
670 (error "No such file: %L; Load header: %L"
671 file directive)))))))
672 (list (remove nil loads) ellipsisp (remove nil files)))))
673
674 675 676 677 678 (defun ClassifyFiles (files subsets)
679 (let ((dpl (list nil)))
680 (foreach file files
681 (exists subset subsets
682 (let ((suffixes (car subset)))
683 (exists suffix suffixes
684 (when (SuffixP suffix file)
685 (letseq ((key (cadr subset))
686 (accum (get dpl key)))
687 (putprop dpl (cons file accum) key)))))))
688 dpl))
689
690 691 (define fileSubsets
692 '( 693 (("-test.ils" "-test.il") tests)
694 695 696 ((".ils" ".il") sources)))
697
698 699 (defun FinalizePackageInfo (base pkg headerInfo loads ellipsisp files)
700 (letseq ((filesInfo (ClassifyFiles files fileSubsets))
701 (sources (cond
702 ((and loads ellipsisp)
703 (append loads filesInfo->sources))
704 (loads)
705 (t
706 filesInfo->sources))))
707 (makeInstance 'VedaTestingPackageFilesystemInfo
708 ?name pkg
709 ?description headerInfo->description
710 ?authors headerInfo->authors
711 ?base base
712 ?header headerInfo->header
713 ?requires headerInfo->requires
714 ?sources sources
715 ?tests filesInfo->tests)))
716
717 718 719 720 721 (defun PackageInfoFromBase (base pkg headerInfo)
722 (letseq ((dir (strcat base pkg "/"))
723 (files (getDirFiles dir)))
724 (apply FinalizePackageInfo base pkg headerInfo
725 (if headerInfo->load
726 (ProcessLoadDirective headerInfo->load files)
727 (list nil t files)))))
728
729 730 731 732 733 734 735 (defun PackageInfoFromFilesystem (pkg bases)
736 (let (info)
737 (exists base bases
738 (let ((dir (strcat base pkg)))
739 (exists suffix '(".il" ".ils")
740 (letseq ((filename (strcat pkg suffix))
741 (path (strcat dir "/" filename))
742 (headerInfo (ParseHeader path filename)))
743 (when headerInfo
744 (setq info (PackageInfoFromBase base pkg headerInfo)))))))
745 info))
746
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 (defun PackageInfo (pkg @key options)
766 (cond
767 ((isCallable 'VedaPackageInfoHook)
768 (funcall 'VedaPackageInfoHook pkg))
769 (t
770 (let ((options (or options defaultOptions)))
771 (PackageInfoFromFilesystem pkg (GetSearchPath options))))))
772
773 (Export 'PackageInfo PackageInfo)
774
775 776 777 778 (defun LoaderFromPackageInfo (info subset)
779 (let (files)
780 (when (setq files (get info subset))
781 (let ((dir (strcat info->base info->name "/")))
782 (FilesLoader files ?dir dir)))))
783
784 785 786 (defun LoadSubsetFromPackageInfo (info subset)
787 (let ((loader (LoaderFromPackageInfo info subset)))
788 (if loader
789 (funcall loader ?subset subset)
790 (error "Don't know how to load %s from package %L."
791 subset info)))
792 t)
793
794 795 (defun RequirePackageRec (pkg loaded options noPackageRequiresP)
796 (let (info)
797 (cond
798 ((arrayref loaded pkg))
799 ((setq info (PackageInfo pkg ?options options))
800 (unless noPackageRequiresP
801 (foreach require info->requires
802 (RequirePackageRec require loaded options noPackageRequiresP)))
803 (LoadSubsetFromPackageInfo info 'sources)
804 (setarray loaded pkg info))
805 (t
806 (error "Package %L not found." pkg)))))
807
808 809 810 811 812 813 (defun RequirePackage (pkg loaded options)
814 (letseq ((requires (GetRequires options))
815 (noPackageRequiresP (neq requires t)))
816 (when (pairp requires)
817 (foreach require requires
818 (RequirePackageRec require loaded options noPackageRequiresP)))
819 (RequirePackageRec pkg loaded options noPackageRequiresP)))
820
821 822 823 824 (defun BootstrapPackageInfo (filename)
825 (let ((n (strlen filename))
826 (suffix (rindex filename "/")))
827 (cond
828 (suffix
829 (let ((slen (strlen suffix)))
830 (let ((pkg (substring suffix 2 (slen - 5)))
831 (dir (strcat (substring filename 1 (n - slen + 1)) "../")))
832 (PackageInfoFromFilesystem pkg (list dir)))))
833 (t
834 (let ((pkg (substring filename 1 (n - 4))))
835 (PackageInfoFromFilesystem pkg '("../")))))))
836
837 838 839 840 (define defaultLoadedTable
841 (let ((loaded (makeTable 'loaded nil))
842 (thisFilename (get_filename piport)))
843 (when (and thisFilename (SuffixP ".ils" thisFilename))
844 (let ((info (BootstrapPackageInfo thisFilename)))
845 (when info
846 (setarray loaded (GetName info) info))))
847 loaded))
848
849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 (defun PackageLoad (pkg @key options)
865 (letseq ((options (or options defaultOptions))
866 (forceReload (GetForceReload options)))
867 (cond
868 ((eq forceReload t)
869 (setq defaultLoadedTable (makeTable 'loaded nil)))
870 ((pairp forceReload)
871 (foreach pkg forceReload
872 (remove pkg defaultLoadedTable))))
873 (RequirePackage pkg defaultLoadedTable options)))
874
875 (Export 'PackageLoad PackageLoad)
876
877 878 879 (defun MakeLoadedTable (preloaded)
880 (let ((table (makeTable 'loaded nil)))
881 (foreach key defaultLoadedTable
882 (setarray table key (arrayref defaultLoadedTable key)))
883 (foreach pkg preloaded
884 (unless (arrayref table pkg)
885 (let ((info (or (PackageInfo pkg)
886 (error "Cannot mark %L as preloaded." pkg))))
887 (setarray table pkg info))))
888 table))
889
890 891 892 893 (defun RunTestsOnPackage (pkg @key options)
894 (letseq ((loaded (MakeLoadedTable options->_preloaded))
895 (info (RequirePackage pkg loaded defaultOptions))
896 (pkgPath (when info->base
897 (list info->base)))
898 (testOptions (constar (car options)
899 'package pkg
900 'packagePath pkgPath
901 (cdr options)))
902 (loader (LoaderFromPackageInfo info 'tests))
903 (tests (when loader
904 (LoadTestsUsingLoader loader))))
905 (RunTests tests ?options testOptions)))
906
907 (Export 'RunTestsOnPackage RunTestsOnPackage))
908
909