# 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 testing
-# Extractor, Executor an Reporter for the tests in a module
-class NitUnitExecutor
- super Doc2Mdwn
-
- # The name of the module to import
- var modname: String
+var toolcontext = new ToolContext
- # 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_file, toolcontext.opt_gen_unit, toolcontext.opt_gen_force, toolcontext.opt_gen_private, toolcontext.opt_gen_show)
+toolcontext.tooldescription = "Usage: nitunit [OPTION]... <file.nit>...\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, modname: String, testsuite: HTMLTag)
- do
- super(toolcontext)
- self.prefix = prefix
- self.modname = modname
- 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 block = new 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 return
-
- # Search `assert` in the AST
- var v = new SearchAssertVisitor
- v.enter_visit(ast)
- if not v.foundit then return
-
- # Add it to the file
- block.add(text)
+ if toolcontext.opt_file.value != null then
+ print "Option --target-file cannot be used with --gen-suite"
+ exit(0)
end
-
- # 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
- block.clear
-
- work(ndoc.to_mdoc)
-
- if block.is_empty then return
-
- 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")
- var modname = self.modname
- f.write("import {modname}\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 . >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
- var res = sys.system(cmd)
- var res2 = 0
- if res == 0 then
- res2 = sys.system("./{file}.bin >>'{file}.out1' 2>&1 </dev/null")
- end
-
- var msg
- f = new IFStream.open("{file}.out1")
- var n2
- n2 = new HTMLTag("system-err")
- tc.add n2
- msg = f.read_all
- f.close
-
- n2 = new HTMLTag("system-out")
- tc.add n2
- for text in block do n2.append(text)
-
-
- if res != 0 then
- var ne = new HTMLTag("failure")
- ne.attr("message", msg)
- tc.add ne
- toolcontext.warning(ndoc.location, "FAILURE: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
- toolcontext.modelbuilder.failed_entities += 1
- else if res2 != 0 then
- var ne = new HTMLTag("error")
- ne.attr("message", msg)
- tc.add ne
- toolcontext.warning(ndoc.location, "ERROR: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
- toolcontext.modelbuilder.failed_entities += 1
- end
- toolcontext.check_errors
-
- testsuite.add(tc)
+else
+ if toolcontext.opt_gen_force.value then
+ print "Option --force must be used with --gen-suite"
+ exit(0)
end
-end
-
-class SearchAssertVisitor
- super Visitor
- var foundit = false
- redef fun visit(node)
- do
- if foundit then
- return
- else if node isa AAssertExpr then
- foundit = true
- return
- else
- node.visit_all(self)
- end
+ if toolcontext.opt_gen_private.value then
+ print "Option --private must be used with --gen-suite"
+ exit(0)
+ end
+ if toolcontext.opt_gen_show.value then
+ print "Option --only-show must be used with --gen-suite"
+ exit(0)
end
end
-redef class ModelBuilder
- var total_entities = 0
- var doc_entities = 0
- var unit_entities = 0
- var failed_entities = 0
-
- fun test_markdown(mmodule: MModule): HTMLTag
- do
- var ts = new HTMLTag("testsuite")
- toolcontext.info("nitunit: {mmodule}", 2)
- if not mmodule2nmodule.has_key(mmodule) then return ts
-
- var nmodule = mmodule2nmodule[mmodule]
- assert nmodule != null
+var model = new Model
+var modelbuilder = new ModelBuilder(model, toolcontext)
- # what module to import in the unit test.
- # try to detect the main module of the project
- # TODO do things correctly once the importation of arbitraty nested module is legal
- var o = mmodule
- var g = o.mgroup
- if g != null then
- o = get_mmodule_by_name(nmodule, g, g.mproject.name).as(not null)
- end
+var module_files = modelbuilder.filter_nit_source(args)
- ts.attr("package", mmodule.full_name)
+var mmodules = modelbuilder.parse_full(module_files)
+modelbuilder.run_phases
- var prefix = toolcontext.opt_dir.value
- if prefix == null then prefix = ".nitunit"
- prefix = prefix.join_path(mmodule.to_s)
- var d2m = new NitUnitExecutor(toolcontext, prefix, o.name, ts)
+if toolcontext.opt_gen_unit.value then
+ modelbuilder.gen_test_unit(mmodules.first)
+ exit(0)
+end
- var tc
+var page = new HTMLTag("testsuites")
- do
- total_entities += 1
- var nmoduledecl = nmodule.n_moduledecl
- if nmoduledecl == null then break label x
- var ndoc = nmoduledecl.n_doc
- if ndoc == null then break label x
- doc_entities += 1
- tc = new HTMLTag("testcase")
- # NOTE: jenkins expects a '.' in the classname attr
- tc.attr("classname", mmodule.full_name + ".<module>")
- tc.attr("name", "<module>")
- 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", "<class>")
- 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)
+ page.add modelbuilder.test_mdoc(mdoc)
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.get_mgroup(a)
+ if g == null then continue
+ page.add modelbuilder.test_group(g)
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]... <file.nit>...\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)
+ page.add modelbuilder.test_unit(m)
end
var file = toolcontext.opt_output.value
if file == null then file = "nitunit.xml"
page.write_to_file(file)
-print "Results saved in {file}"
-
+# print docunits results
+print "DocUnits:"
if modelbuilder.unit_entities == 0 then
- print "No nitunits found"
+ print "No doc units found"
else if modelbuilder.failed_entities == 0 and not toolcontext.opt_noact.value then
- print "Success"
+ print "DocUnits Success"
end
print "Entities: {modelbuilder.total_entities}; Documented ones: {modelbuilder.doc_entities}; With nitunits: {modelbuilder.unit_entities}; Failures: {modelbuilder.failed_entities}"
+# print testsuites results
+print "\nTestSuites:"
+if modelbuilder.total_tests == 0 then
+ print "No test cases found"
+else if modelbuilder.failed_tests == 0 and not toolcontext.opt_noact.value then
+ print "TestSuites Success"
+end
+print "Class suites: {modelbuilder.total_classes}; Test Cases: {modelbuilder.total_tests}; Failures: {modelbuilder.failed_tests}"