X-Git-Url: http://nitlanguage.org diff --git a/src/nitunit.nit b/src/nitunit.nit index b605894..125c514 100644 --- a/src/nitunit.nit +++ b/src/nitunit.nit @@ -12,296 +12,156 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Program to extract and execute unit tests from nit source files +# Testing tool. +# see `testing/README` module nitunit -import modelize_property -intrude import markdown -import parser_util +import frontend +import testing -# Extractor, Executor an Reporter for the tests in a module -class NitUnitExecutor - super Doc2Mdwn - - # The module to import - var mmodule: MModule +var toolcontext = new ToolContext +toolcontext.keep_going = true - # The prefix of the generated Nit source-file - var prefix: String +toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir, toolcontext.opt_noact, toolcontext.opt_pattern, toolcontext.opt_autosav, toolcontext.opt_gen_unit, toolcontext.opt_gen_force, toolcontext.opt_gen_private, toolcontext.opt_gen_show, toolcontext.opt_nitc) +toolcontext.tooldescription = "Usage: nitunit [OPTION]... ...\nExecutes the unit tests from Nit source files." - # The XML node associated to the module - var testsuite: HTMLTag +toolcontext.process_options(args) +var args = toolcontext.option_context.rest - # 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 +if toolcontext.opt_gen_unit.value then + if toolcontext.opt_pattern.value != null then + print "Option --pattern cannot be used with --gen-suite" + exit(0) end - - # All blocks of code from a same `ADoc` - var blocks = new Array[Array[String]] - - redef fun process_code(n: HTMLTag, text: String) - do - # Try to parse it - var ast = toolcontext.parse_something(text) - - # 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 - return - end - - # Create a first block - # Or create a new block for modules that are more than a main part - if blocks.is_empty or ast isa AModule then - blocks.add(new Array[String]) - end - - # Add it to the file - blocks.last.add(text) +else + if toolcontext.opt_gen_force.value then + print "Option --force must be used with --gen-suite" + exit(0) end - - # The associated node to localize warnings - var ndoc: nullable ADoc - - # 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) - do - blocks.clear - - self.ndoc = ndoc - - work(ndoc.to_mdoc) - toolcontext.check_errors - - if blocks.is_empty then return - - for block in blocks do test_block(ndoc, tc, block) + if toolcontext.opt_gen_private.value then + print "Option --private must be used with --gen-suite" + exit(0) end - - # Execute a block - fun test_block(ndoc: ADoc, tc: HTMLTag, block: Array[String]) - do - toolcontext.modelbuilder.unit_entities += 1 - - cpt += 1 - var file = "{prefix}{cpt}.nit" - - toolcontext.info("Execute {tc.attrs["classname"]}.{tc.attrs["name"]} in {file}", 1) - - 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) - end - f.close - - if toolcontext.opt_noact.value then return - - var nit_dir = toolcontext.nit_dir - var nitg = "{nit_dir}/bin/nitg" - if nit_dir == null or not nitg.file_exists then - toolcontext.error(null, "Cannot find nitg. Set envvar NIT_DIR.") - toolcontext.check_errors - end - var cmd = "{nitg} --ignore-visibility --no-color '{file}' -I {mmodule.location.file.filename.dirname} >'{file}.out1' 2>&1 >'{file}.out1' 2>&1 ") - tc.attr("name", "") - d2m.extract(ndoc, tc) - end label x - for nclassdef in nmodule.n_classdefs do - var mclassdef = nclassdef.mclassdef.as(not null) - if nclassdef isa AStdClassdef then - total_entities += 1 - var ndoc = nclassdef.n_doc - if ndoc != null then - doc_entities += 1 - tc = new HTMLTag("testcase") - tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name) - tc.attr("name", "") - d2m.extract(ndoc, tc) - end - end - for npropdef in nclassdef.n_propdefs do - var mpropdef = npropdef.mpropdef.as(not null) - total_entities += 1 - var ndoc = npropdef.n_doc - if ndoc != null then - doc_entities += 1 - tc = new HTMLTag("testcase") - tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name) - tc.attr("name", mpropdef.mproperty.full_name) - d2m.extract(ndoc, tc) - end - end - end +if toolcontext.opt_full.value then mmodules = model.mmodules - return ts +for a in args do + if not a.file_exists then + toolcontext.fatal_error(null, "Error: cannot load file or module `{a}`.") end + # Try to load the file as a markdown document + var mdoc = modelbuilder.load_markdown(a) + var ts = modelbuilder.test_mdoc(mdoc) + if not ts.children.is_empty then page.add ts end -redef class ToolContext - var opt_full = new OptionBool("Process also imported modules", "--full") - var opt_output = new OptionString("Output name (default is 'nitunit.xml')", "-o", "--output") - var opt_dir = new OptionString("Working directory (default is '.nitunit')", "--dir") - var opt_noact = new OptionBool("Does not compile and run tests", "--no-act") +for a in module_files do + var g = modelbuilder.identify_group(a) + if g == null then continue + var ts = modelbuilder.test_group(g) + if not ts.children.is_empty then page.add ts end -var toolcontext = new ToolContext - -toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir, toolcontext.opt_noact) -toolcontext.tooldescription = "Usage: nitunit [OPTION]... ...\nExecutes the unit tests from Nit source files." - -toolcontext.process_options(args) -var args = toolcontext.option_context.rest - -var model = new Model -var modelbuilder = new ModelBuilder(model, toolcontext) - -var mmodules = modelbuilder.parse(args) -modelbuilder.run_phases - -var page = new HTMLTag("testsuites") - -if toolcontext.opt_full.value then mmodules = model.mmodules - for m in mmodules do - page.add modelbuilder.test_markdown(m) + var ts + ts = modelbuilder.test_markdown(m) + if not ts.children.is_empty then page.add ts + ts = modelbuilder.test_unit(m) + if ts != null and not ts.children.is_empty then page.add ts end var file = toolcontext.opt_output.value if file == null then file = "nitunit.xml" page.write_to_file(file) -print "Results saved in {file}" -if modelbuilder.unit_entities == 0 then - print "No nitunits found" -else if modelbuilder.failed_entities == 0 and not toolcontext.opt_noact.value then - print "Success" +# Print results +printn "Docunits: Entities: {modelbuilder.total_entities}; Documented ones: {modelbuilder.doc_entities}; With nitunits: {modelbuilder.unit_entities}" +if modelbuilder.unit_entities == 0 or toolcontext.opt_noact.value then + print "" +else + printn "; Failures: " + var cpt = modelbuilder.failed_entities + if toolcontext.opt_no_color.value then + print cpt + else if cpt == 0 then + print "0".green.bold + else + print cpt.to_s.red.bold + end +end +printn "Test suites: Classes: {modelbuilder.total_classes}; Test Cases: {modelbuilder.total_tests}" +if modelbuilder.total_tests == 0 or toolcontext.opt_noact.value then + print "" +else + printn "; Failures: " + var cpt = modelbuilder.failed_tests + if toolcontext.opt_no_color.value then + print cpt + else if cpt == 0 then + print "0".green.bold + else + print cpt.to_s.red.bold + end +end + +var total = modelbuilder.unit_entities + modelbuilder.total_tests +var fail = modelbuilder.failed_entities + modelbuilder.failed_tests +if toolcontext.opt_noact.value then + # nothing +else if total == 0 then + var head = "[NOTHING]" + if not toolcontext.opt_no_color.value then + head = head.yellow + end + print "{head} No unit tests to execute." +else if fail == 0 then + var head = "[SUCCESS]" + if not toolcontext.opt_no_color.value then + head = head.green.bold + end + print "{head} All {total} tests passed." +else + var head = "[FAILURE]" + if not toolcontext.opt_no_color.value then + head = head.red.bold + end + print "{head} {fail}/{total} tests failed." + + print "`{test_dir}` is not removed for investigation." + exit 1 end -print "Entities: {modelbuilder.total_entities}; Documented ones: {modelbuilder.doc_entities}; With nitunits: {modelbuilder.unit_entities}; Failures: {modelbuilder.failed_entities}" + +test_dir.rmdir