Merge: Added contributing guidelines and link from readme
[nit.git] / src / testing / testing_doc.nit
index cfaa912..75e0830 100644 (file)
@@ -36,8 +36,8 @@ class NitUnitExecutor
        # The XML node associated to the module
        var testsuite: HTMLTag
 
-       # All failures from a same `ADoc`
-       var failures = new Array[String]
+       # The name of the suite
+       var name: String
 
        # Markdown processor used to parse markdown comments and extract code.
        var mdproc = new MarkdownProcessor
@@ -66,7 +66,6 @@ class NitUnitExecutor
        fun extract(mdoc: MDoc, xml_classname, xml_name: String)
        do
                last_docunit = null
-               failures.clear
                self.xml_classname = xml_classname
                self.xml_name = xml_name
 
@@ -74,38 +73,64 @@ class NitUnitExecutor
 
                # Populate `blocks` from the markdown decorator
                mdproc.process(mdoc.content.join("\n"))
-
-               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.unit_entities += 1
-                               toolcontext.modelbuilder.failed_entities += 1
-                       end
-                       if last_docunit == null then testsuite.add(tc)
-               end
        end
 
        # All extracted docunits
        var docunits = new Array[DocUnit]
 
+       fun show_status
+       do
+               toolcontext.show_unit_status(name, docunits)
+       end
+
+       fun mark_done(du: DocUnit)
+       do
+               du.is_done = true
+               toolcontext.clear_progress_bar
+               toolcontext.show_unit(du)
+               show_status
+       end
+
        # Execute all the docunits
        fun run_tests
        do
+               if docunits.is_empty then
+                       return
+               end
+
+               # Try to group each nitunit into a single source file to fasten the compilation
                var simple_du = new Array[DocUnit]
+               show_status
                for du in docunits do
+                       # Skip existing errors
+                       if du.error != null then
+                               continue
+                       end
+
                        var ast = toolcontext.parse_something(du.block)
                        if ast isa AExpr then
                                simple_du.add du
+                       end
+               end
+               test_simple_docunits(simple_du)
+
+               # Now test them in order
+               for du in docunits do
+                       if du.error != null then
+                               # Nothing to execute. Conclude
+                       else if du.test_file != null then
+                               # Already compiled. Execute it.
+                               execute_simple_docunit(du)
                        else
+                               # Need to try to compile it, then execute it
                                test_single_docunit(du)
                        end
+                       mark_done(du)
                end
 
-               test_simple_docunits(simple_du)
+               # Final status
+               show_status
+               print ""
 
                for du in docunits do
                        testsuite.add du.to_xml
@@ -149,28 +174,35 @@ class NitUnitExecutor
 
                if res != 0 then
                        # Compilation error.
-                       # Fall-back to individual modes:
-                       for du in dus do
-                               test_single_docunit(du)
-                       end
+                       # They will be executed independently
                        return
                end
 
+               # Compilation was a success.
+               # Store what need to be executed for each one.
                i = 0
                for du in dus do
                        i += 1
-                       toolcontext.info("Execute doc-unit {du.full_name} in {file} {i}", 1)
-                       var res2 = toolcontext.safe_exec("{file.to_program_name}.bin {i} >'{file}.out1' 2>&1 </dev/null")
+                       du.test_file = file
+                       du.test_arg = i
+               end
+       end
 
-                       var content = "{file}.out1".to_path.read_all
-                       var msg = content.trunc(8192).filter_nonprintable
+       # Execute a docunit compiled by `test_single_docunit`
+       fun execute_simple_docunit(du: DocUnit)
+       do
+               var file = du.test_file.as(not null)
+               var i = du.test_arg.as(not null)
+               toolcontext.info("Execute doc-unit {du.full_name} in {file} {i}", 1)
+               var res2 = toolcontext.safe_exec("{file.to_program_name}.bin {i} >'{file}.out1' 2>&1 </dev/null")
+               du.was_exec = true
 
-                       if res2 != 0 then
-                               du.error = content
-                               toolcontext.warning(du.location, "error", "ERROR: {du.full_name} (in {file}): Runtime error\n{msg}")
-                               toolcontext.modelbuilder.failed_entities += 1
-                       end
-                       toolcontext.check_errors
+               var content = "{file}.out1".to_path.read_all
+               du.raw_output = content
+
+               if res2 != 0 then
+                       du.error = "Runtime error in {file} with argument {i}"
+                       toolcontext.modelbuilder.failed_entities += 1
                end
        end
 
@@ -194,20 +226,19 @@ class NitUnitExecutor
                var res2 = 0
                if res == 0 then
                        res2 = toolcontext.safe_exec("{file.to_program_name}.bin >'{file}.out1' 2>&1 </dev/null")
+                       du.was_exec = true
                end
 
                var content = "{file}.out1".to_path.read_all
-               var msg = content.trunc(8192).filter_nonprintable
+               du.raw_output = content
 
                if res != 0 then
-                       du.error = content
-                       toolcontext.warning(du.location, "failure", "FAILURE: {du.full_name} (in {file}):\n{msg}")
+                       du.error = "Compilation error in {file}"
                        toolcontext.modelbuilder.failed_entities += 1
                else if res2 != 0 then
-                       toolcontext.warning(du.location, "error", "ERROR: {du.full_name} (in {file}):\n{msg}")
+                       du.error = "Runtime error in {file}"
                        toolcontext.modelbuilder.failed_entities += 1
                end
-               toolcontext.check_errors
        end
 
        # Create and fill the header of a unit file `file`.
@@ -243,7 +274,7 @@ class NitUnitExecutor
                if mmodule != null then
                        opts.add "-I {mmodule.filepath.dirname}"
                end
-               var cmd = "{nitc} --ignore-visibility --no-color '{file}' {opts.join(" ")} >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
+               var cmd = "{nitc} --ignore-visibility --no-color -q '{file}' {opts.join(" ")} >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
                var res = toolcontext.safe_exec(cmd)
                return res
        end
@@ -299,8 +330,11 @@ private class NitunitDecorator
                                message = "Error: Invalid Nit code."
                        end
 
-                       executor.toolcontext.warning(location, "invalid-block", "{message} To suppress this message, enclose the block with a fence tagged `nitish` or `raw` (see `man nitdoc`).")
-                       executor.failures.add("{location}: {message}")
+                       var du = new_docunit
+                       du.block += code
+                       du.error_location = location
+                       du.error = message
+                       executor.toolcontext.modelbuilder.failed_entities += 1
                        return
                end
 
@@ -331,11 +365,11 @@ private class NitunitDecorator
                var mdoc = executor.mdoc
                assert mdoc != null
 
-               var next_number = 0
+               var next_number = 1
                var name = executor.xml_name
                if executor.docunits.not_empty and executor.docunits.last.mdoc == mdoc then
                        next_number = executor.docunits.last.number + 1
-                       name += "+" + next_number.to_s
+                       name += "#" + next_number.to_s
                end
 
                var res = new DocUnit(mdoc, next_number, "", executor.xml_classname, name)
@@ -358,11 +392,26 @@ class DocUnit
        # The numbering of self in mdoc (starting with 0)
        var number: Int
 
-       # The name of the unit to show in messages
-       fun full_name: String do
+       # The generated Nit source file that contains the unit-test
+       #
+       # Note that a same generated file can be used for multiple tests.
+       # See `test_arg` that is used to distinguish them
+       var test_file: nullable String = null
+
+       # The command-line argument to use when executing the test, if any.
+       var test_arg: nullable Int = null
+
+       redef fun full_name do
                var mentity = mdoc.original_mentity
-               if mentity != null then return mentity.full_name
-               return xml_classname + "." + xml_name
+               if mentity != null then
+                       var res = mentity.full_name
+                       if number > 1 then
+                               res += "#{number}"
+                       end
+                       return res
+               else
+                       return xml_classname + "." + xml_name
+               end
        end
 
        # The text of the code to execute.
@@ -384,7 +433,7 @@ class DocUnit
        #
        # If `self` is made of multiple code-blocks, then the location
        # starts at the first code-books and finish at the last one, thus includes anything between.
-       var location: Location is lazy do
+       redef var location is lazy do
                return new Location(mdoc.location.file, lines.first, lines.last+1, columns.first+1, 0)
        end
 
@@ -450,7 +499,7 @@ redef class ModelBuilder
 
                var prefix = toolcontext.test_dir
                prefix = prefix.join_path(mmodule.to_s)
-               var d2m = new NitUnitExecutor(toolcontext, prefix, o, ts)
+               var d2m = new NitUnitExecutor(toolcontext, prefix, o, ts, "Docunits of module {mmodule.full_name}")
 
                do
                        total_entities += 1
@@ -504,7 +553,7 @@ redef class ModelBuilder
 
                var prefix = toolcontext.test_dir
                prefix = prefix.join_path(mgroup.to_s)
-               var d2m = new NitUnitExecutor(toolcontext, prefix, o, ts)
+               var d2m = new NitUnitExecutor(toolcontext, prefix, o, ts, "Docunits of group {mgroup.full_name}")
 
                total_entities += 1
                var mdoc = mgroup.mdoc
@@ -523,14 +572,14 @@ redef class ModelBuilder
        fun test_mdoc(mdoc: MDoc): HTMLTag
        do
                var ts = new HTMLTag("testsuite")
-               var file = mdoc.location.to_s
+               var file = mdoc.location.file.filename
 
                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 d2m = new NitUnitExecutor(toolcontext, prefix, null, ts, "Docunits of file {file}")
 
                total_entities += 1
                doc_entities += 1