pep8analysis: add copyright info for viz.js
[nit.git] / src / nitunit.nit
index 5bb2b67..e2404ca 100644 (file)
@@ -23,8 +23,8 @@ import parser_util
 class NitUnitExecutor
        super Doc2Mdwn
 
-       # The name of the module to import
-       var modname: String
+       # The module to import
+       var mmodule: MModule
 
        # The prefix of the generated Nit source-file
        var prefix: String
@@ -33,16 +33,16 @@ class NitUnitExecutor
        var testsuite: HTMLTag
 
        # Initialize a new e
-       init(toolcontext: ToolContext, prefix: String, modname: String, testsuite: HTMLTag)
+       init(toolcontext: ToolContext, prefix: String, mmodule: MModule, testsuite: HTMLTag)
        do
                super(toolcontext)
                self.prefix = prefix
-               self.modname = modname
+               self.mmodule = mmodule
                self.testsuite = testsuite
        end
 
        # All blocks of code from a same `ADoc`
-       var block = new Array[String]
+       var blocks = new Array[Array[String]]
 
        redef fun process_code(n: HTMLTag, text: String)
        do
@@ -50,17 +50,39 @@ class NitUnitExecutor
                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
+               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 return
+               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
-               block.add(text)
+               blocks.last.add(text)
        end
 
+       # The associated node to localize warnings
+       var ndoc: nullable ADoc
+
        # used to generate distinct names
        var cpt = 0
 
@@ -68,16 +90,27 @@ class NitUnitExecutor
        # Fill the prepated `tc` (testcase) XTM node
        fun extract(ndoc: ADoc, tc: HTMLTag)
        do
-               block.clear
+               blocks.clear
+
+               self.ndoc = ndoc
+
+               work(ndoc.to_mdoc)
+               toolcontext.check_errors
+
+               if blocks.is_empty then return
 
-               work(ndoc)
+               for block in blocks do test_block(ndoc, tc, block)
+       end
 
-               if block.is_empty then return
+       # 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}", 2)
+               toolcontext.info("Execute {tc.attrs["name"]} in {file}", 1)
 
                var dir = file.dirname
                if dir != "" then dir.mkdir
@@ -85,15 +118,22 @@ class NitUnitExecutor
                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("import {mmodule.name}\n")
                f.write("\n")
                for text in block do
                        f.write(text)
                end
                f.close
 
-               var cmd = "../bin/nitg --no-color '{file}' -I . >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
+               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 </dev/null -o '{file}.bin'"
                var res = sys.system(cmd)
                var res2 = 0
                if res == 0 then
@@ -118,11 +158,13 @@ class NitUnitExecutor
                        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
 
@@ -147,6 +189,11 @@ class SearchAssertVisitor
 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")
@@ -161,8 +208,8 @@ redef class ModelBuilder
                # 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, mmodule, g.mproject.name).as(not null)
+               if g != null and g.mproject.name == "standard" then
+                       o = get_mmodule_by_name(nmodule, g, g.mproject.name).as(not null)
                end
 
                ts.attr("package", mmodule.full_name)
@@ -170,15 +217,17 @@ redef class ModelBuilder
                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)
+               var d2m = new NitUnitExecutor(toolcontext, prefix, o, ts)
 
                var tc
 
                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>")
@@ -188,8 +237,10 @@ redef class ModelBuilder
                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>")
@@ -198,8 +249,10 @@ redef class ModelBuilder
                        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)
@@ -216,20 +269,16 @@ 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")
 end
 
 var toolcontext = new ToolContext
 
-toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir)
-
+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
+toolcontext.process_options(args)
 var args = toolcontext.option_context.rest
-if args.is_empty or toolcontext.opt_help.value then
-       print "usage: nitunit [options] file.nit..."
-       toolcontext.option_context.usage
-       return
-end
 
 var model = new Model
 var modelbuilder = new ModelBuilder(model, toolcontext)
@@ -247,4 +296,12 @@ end
 
 var file = toolcontext.opt_output.value
 if file == null then file = "nitunit.xml"
-page.save(file)
+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"
+end
+print "Entities: {modelbuilder.total_entities}; Documented ones: {modelbuilder.doc_entities}; With nitunits: {modelbuilder.unit_entities}; Failures: {modelbuilder.failed_entities}"