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