X-Git-Url: http://nitlanguage.org diff --git a/src/testing/testing_doc.nit b/src/testing/testing_doc.nit index e370b8a..7d4afe6 100644 --- a/src/testing/testing_doc.nit +++ b/src/testing/testing_doc.nit @@ -16,56 +16,49 @@ module testing_doc import testing_base -intrude import markdown +intrude import docdown # Extractor, Executor and Reporter for the tests in a module class NitUnitExecutor super Doc2Mdwn - # The module to import - var mmodule: MModule - # The prefix of the generated Nit source-file var prefix: String + # The module to import, if any + var mmodule: nullable MModule + # The XML node associated to the module var testsuite: HTMLTag - # Initialize a new e - init(toolcontext: ToolContext, prefix: String, mmodule: MModule, testsuite: HTMLTag) - do - super(toolcontext) - self.prefix = prefix - self.mmodule = mmodule - self.testsuite = testsuite - end - # All blocks of code from a same `ADoc` var blocks = new Array[Array[String]] - redef fun process_code(n: HTMLTag, text: String) + # All failures from a same `ADoc` + var failures = new Array[String] + + redef fun process_code(n: HTMLTag, text: String, tag: nullable String) do + # Skip non-blocks + if n.tag != "pre" then return + + # Skip strict non-nit + if tag != null and tag != "nit" and tag != "" then + return + end + # Try to parse it var ast = toolcontext.parse_something(text) + # Skip pure comments + if ast isa TComment then return + # We want executable code if not (ast isa AModule or ast isa ABlockExpr or ast isa AExpr) then - if ndoc != null and n.tag == "pre" and toolcontext.opt_warn.value > 1 then - toolcontext.warning(ndoc.location, "Warning: There is a block of code that is not valid Nit, thus not considered a nitunit") - if ast isa AError then toolcontext.warning(ast.location, ast.message) - ndoc = null # To avoid multiple warning in the same node - end - return - end - - # Search `assert` in the AST - var v = new SearchAssertVisitor - v.enter_visit(ast) - if not v.foundit then - if ndoc != null and n.tag == "pre" and toolcontext.opt_warn.value > 1 then - toolcontext.warning(ndoc.location, "Warning: There is a block of Nit code without `assert`, thus not considered a nitunit") - ndoc = null # To avoid multiple warning in the same node - end + var message = "" + if ast isa AError then message = " At {ast.location}: {ast.message}." + toolcontext.warning(mdoc.location, "invalid-block", "Error: there is a block of invalid Nit code, thus not considered a nitunit. To suppress this warning, enclose the block with a fence tagged `nitish` or `raw` (see `man nitdoc`).{message}") + failures.add("{mdoc.location}: Invalid block of code.{message}") return end @@ -79,68 +72,167 @@ class NitUnitExecutor blocks.last.add(text) end - # The associated node to localize warnings - var ndoc: nullable ADoc + # The associated documentation object + var mdoc: nullable MDoc = null # used to generate distinct names var cpt = 0 # The entry point for a new `ndoc` node - # Fill the prepated `tc` (testcase) XTM node - fun extract(ndoc: ADoc, tc: HTMLTag) + # Fill `docunits` with new discovered unit of tests. + # + # `tc` (testcase) is the pre-filled XML node + fun extract(mdoc: MDoc, tc: HTMLTag) do blocks.clear + failures.clear - self.ndoc = ndoc + self.mdoc = mdoc + + work(mdoc) - work(ndoc.to_mdoc) toolcontext.check_errors + if not failures.is_empty then + for msg in failures do + var ne = new HTMLTag("failure") + ne.attr("message", msg) + tc.add ne + toolcontext.modelbuilder.failed_entities += 1 + end + if blocks.is_empty then testsuite.add(tc) + end + if blocks.is_empty then return - for block in blocks do test_block(ndoc, tc, block) + for block in blocks do + docunits.add new DocUnit(mdoc, tc, block.join("")) + end end - # Execute a block - fun test_block(ndoc: ADoc, tc: HTMLTag, block: Array[String]) + # All extracted docunits + var docunits = new Array[DocUnit] + + # Execute all the docunits + fun run_tests do - toolcontext.modelbuilder.unit_entities += 1 + var simple_du = new Array[DocUnit] + for du in docunits do + var ast = toolcontext.parse_something(du.block) + if ast isa AExpr then + simple_du.add du + else + test_single_docunit(du) + end + end - cpt += 1 - var file = "{prefix}{cpt}.nit" + test_simple_docunits(simple_du) + end - toolcontext.info("Execute doc-unit {tc.attrs["name"]} in {file}", 1) + # Executes multiples doc-units in a shared program. + # Used for docunits simple block of code (without modules, classes, functions etc.) + # + # In case of compilation error, the method fallbacks to `test_single_docunit` to + # * locate exactly the compilation problem in the problematic docunit. + # * permit the execution of the other docunits that may be correct. + fun test_simple_docunits(dus: Array[DocUnit]) + do + if dus.is_empty then return + + var file = "{prefix}-0.nit" var dir = file.dirname if dir != "" then dir.mkdir var f - f = new OFStream.open(file) - f.write("# GENERATED FILE\n") - f.write("# Example extracted from a documentation\n") - f.write("import {mmodule.name}\n") - f.write("\n") - for text in block do - f.write(text) + f = create_unitfile(file) + var i = 0 + for du in dus do + + i += 1 + f.write("fun run_{i} do\n") + f.write("# {du.testcase.attrs["name"]}\n") + f.write(du.block) + f.write("end\n") + end + f.write("var a = args.first.to_i\n") + for j in [1..i] do + f.write("if a == {j} then run_{j}\n") end f.close if toolcontext.opt_noact.value then return - var nit_dir = toolcontext.nit_dir - var nitg = "{nit_dir or else ""}/bin/nitg" - if nit_dir == null or not nitg.file_exists then - toolcontext.error(null, "Cannot find nitg. Set envvar NIT_DIR.") + var res = compile_unitfile(file) + + if res != 0 then + # Compilation error. + # Fall-back to individual modes: + for du in dus do + test_single_docunit(du) + end + return + end + + i = 0 + for du in dus do + var tc = du.testcase + toolcontext.modelbuilder.unit_entities += 1 + i += 1 + toolcontext.info("Execute doc-unit {du.testcase.attrs["name"]} in {file} {i}", 1) + var res2 = sys.system("{file.to_program_name}.bin {i} >>'{file}.out1' 2>&1 >'{file}.out1' 2>&1 >'{file}.out1' 2>&1 '{file}.out1' 2>&1 ` XML element that contains the results of the executions. fun test_markdown(mmodule: MModule): HTMLTag do var ts = new HTMLTag("testsuite") toolcontext.info("nitunit: doc-unit {mmodule}", 2) - if not mmodule2nmodule.has_key(mmodule) then return ts - var nmodule = mmodule2nmodule[mmodule] - assert nmodule != null + var nmodule = mmodule2node(mmodule) + if nmodule == null then return ts # usualy, only the original module must be imported in the unit test. var o = mmodule @@ -228,9 +367,9 @@ redef class ModelBuilder doc_entities += 1 tc = new HTMLTag("testcase") # NOTE: jenkins expects a '.' in the classname attr - tc.attr("classname", mmodule.full_name + ".") + tc.attr("classname", "nitunit." + mmodule.full_name + ".") tc.attr("name", "") - d2m.extract(ndoc, tc) + d2m.extract(ndoc.to_mdoc, tc) end label x for nclassdef in nmodule.n_classdefs do var mclassdef = nclassdef.mclassdef @@ -241,9 +380,9 @@ redef class ModelBuilder if ndoc != null then doc_entities += 1 tc = new HTMLTag("testcase") - tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name) + tc.attr("classname", "nitunit." + mmodule.full_name + "." + mclassdef.mclass.full_name) tc.attr("name", "") - d2m.extract(ndoc, tc) + d2m.extract(ndoc.to_mdoc, tc) end end for npropdef in nclassdef.n_propdefs do @@ -254,13 +393,78 @@ redef class ModelBuilder if ndoc != null then doc_entities += 1 tc = new HTMLTag("testcase") - tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name) + tc.attr("classname", "nitunit." + mmodule.full_name + "." + mclassdef.mclass.full_name) tc.attr("name", mpropdef.mproperty.full_name) - d2m.extract(ndoc, tc) + d2m.extract(ndoc.to_mdoc, tc) end end end + d2m.run_tests + + return ts + end + + # Extracts and executes all the docunits in the readme of the `mgroup` + # Returns a JUnit-compatible `` XML element that contains the results of the executions. + fun test_group(mgroup: MGroup): HTMLTag + do + var ts = new HTMLTag("testsuite") + toolcontext.info("nitunit: doc-unit group {mgroup}", 2) + + # usually, only the default module must be imported in the unit test. + var o = mgroup.default_mmodule + + ts.attr("package", mgroup.full_name) + + var prefix = toolcontext.test_dir + prefix = prefix.join_path(mgroup.to_s) + var d2m = new NitUnitExecutor(toolcontext, prefix, o, ts) + + var tc + + total_entities += 1 + var mdoc = mgroup.mdoc + if mdoc == null then return ts + + doc_entities += 1 + tc = new HTMLTag("testcase") + # NOTE: jenkins expects a '.' in the classname attr + tc.attr("classname", "nitunit." + mgroup.full_name) + tc.attr("name", "") + d2m.extract(mdoc, tc) + + d2m.run_tests + + return ts + end + + # Test a document object unrelated to a Nit entity + fun test_mdoc(mdoc: MDoc): HTMLTag + do + var ts = new HTMLTag("testsuite") + var file = mdoc.location.to_s + + toolcontext.info("nitunit: doc-unit file {file}", 2) + + ts.attr("package", file) + + var prefix = toolcontext.test_dir / "file" + var d2m = new NitUnitExecutor(toolcontext, prefix, null, ts) + + var tc + + total_entities += 1 + doc_entities += 1 + + tc = new HTMLTag("testcase") + # NOTE: jenkins expects a '.' in the classname attr + tc.attr("classname", "nitunit.") + tc.attr("name", file) + + d2m.extract(mdoc, tc) + d2m.run_tests + return ts end end