Merge: Nitunit: mass compile non-simple docunits
[nit.git] / src / testing / testing_doc.nit
index a17c8ab..a77a331 100644 (file)
@@ -44,7 +44,7 @@ class NitUnitExecutor
        var mdproc = new MarkdownProcessor
 
        init do
-               mdproc.emitter.decorator = new NitunitDecorator(self)
+               mdproc.decorator = new NitunitDecorator(self)
        end
 
        # The associated documentation object
@@ -100,7 +100,8 @@ class NitUnitExecutor
                end
 
                # Try to group each nitunit into a single source file to fasten the compilation
-               var simple_du = new Array[DocUnit]
+               var simple_du = new Array[DocUnit] # du that are simple statements
+               var single_du = new Array[DocUnit] # du that are modules or include classes
                show_status
                for du in docunits do
                        # Skip existing errors
@@ -111,19 +112,27 @@ class NitUnitExecutor
                        var ast = toolcontext.parse_something(du.block)
                        if ast isa AExpr then
                                simple_du.add du
+                       else
+                               single_du.add du
                        end
                end
-               test_simple_docunits(simple_du)
+
+               # Try to mass compile all the simple du as a single nit module
+               compile_simple_docunits(simple_du)
+               # Try to mass compile all the single du in a single nitc invocation with many modules
+               compile_single_docunits(single_du)
+               # If the mass compilation fail, then each one will be compiled individually
 
                # 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
+                       else if du.is_compiled then
                                # Already compiled. Execute it.
                                execute_simple_docunit(du)
                        else
-                               # Need to try to compile it, then execute it
+                               # A mass compilation failed
+                               # Need to try to recompile it, then execute it
                                test_single_docunit(du)
                        end
                        mark_done(du)
@@ -138,25 +147,29 @@ class NitUnitExecutor
                end
        end
 
-       # Executes multiples doc-units in a shared program.
+       # Compiles 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
+       # In case of success, the docunits are compiled and the caller can call `execute_simple_docunit`.
+       #
+       # In case of compilation error, the docunits are let uncompiled.
+       # The caller should 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])
+       fun compile_simple_docunits(dus: Array[DocUnit])
        do
                if dus.is_empty then return
 
                var file = "{prefix}-0.nit"
 
+               toolcontext.info("Compile {dus.length} simple(s) doc-unit(s) in {file}", 1)
+
                var dir = file.dirname
                if dir != "" then dir.mkdir
                var f
                f = create_unitfile(file)
                var i = 0
                for du in dus do
-
                        i += 1
                        f.write("fun run_{i} do\n")
                        f.write("# {du.full_name}\n")
@@ -175,7 +188,7 @@ class NitUnitExecutor
 
                if res != 0 then
                        # Compilation error.
-                       # They will be executed independently
+                       # They should be generated and compiled independently
                        return
                end
 
@@ -186,6 +199,7 @@ class NitUnitExecutor
                        i += 1
                        du.test_file = file
                        du.test_arg = i
+                       du.is_compiled = true
                end
        end
 
@@ -193,7 +207,7 @@ class NitUnitExecutor
        fun execute_simple_docunit(du: DocUnit)
        do
                var file = du.test_file.as(not null)
-               var i = du.test_arg.as(not null)
+               var i = du.test_arg or else 0
                toolcontext.info("Execute doc-unit {du.full_name} in {file} {i}", 1)
                var clock = new Clock
                var res2 = toolcontext.safe_exec("{file.to_program_name}.bin {i} >'{file}.out1' 2>&1 </dev/null")
@@ -209,41 +223,45 @@ class NitUnitExecutor
                end
        end
 
-       # Executes a single doc-unit in its own program.
-       # Used for docunits larger than a single block of code (with modules, classes, functions etc.)
-       fun test_single_docunit(du: DocUnit)
+       # Produce a single unit file for the docunit `du`.
+       fun generate_single_docunit(du: DocUnit): String
        do
                cpt += 1
                var file = "{prefix}-{cpt}.nit"
 
-               toolcontext.info("Execute doc-unit {du.full_name} in {file}", 1)
-
                var f
                f = create_unitfile(file)
                f.write(du.block)
                f.close
 
+               du.test_file = file
+               return file
+       end
+
+       # Executes a single doc-unit in its own program.
+       # Used for docunits larger than a single block of code (with modules, classes, functions etc.)
+       fun test_single_docunit(du: DocUnit)
+       do
+               var file = generate_single_docunit(du)
+
+               toolcontext.info("Compile doc-unit {du.full_name} in {file}", 1)
+
                if toolcontext.opt_noact.value then return
 
                var res = compile_unitfile(file)
-               var res2 = 0
-               if res == 0 then
-                       var clock = new Clock
-                       res2 = toolcontext.safe_exec("{file.to_program_name}.bin >'{file}.out1' 2>&1 </dev/null")
-                       if not toolcontext.opt_no_time.value then du.real_time = clock.total
-                       du.was_exec = true
-               end
-
                var content = "{file}.out1".to_path.read_all
                du.raw_output = content
 
+               du.test_file = file
+
                if res != 0 then
                        du.error = "Compilation error in {file}"
                        toolcontext.modelbuilder.failed_entities += 1
-               else if res2 != 0 then
-                       du.error = "Runtime error in {file}"
-                       toolcontext.modelbuilder.failed_entities += 1
+                       return
                end
+
+               du.is_compiled = true
+               execute_simple_docunit(du)
        end
 
        # Create and fill the header of a unit file `file`.
@@ -269,7 +287,7 @@ class NitUnitExecutor
                return f
        end
 
-       # Compile an unit file and return the compiler return code
+       # Compile a unit file and return the compiler return code
        #
        # Can terminate the program if the compiler is not found
        private fun compile_unitfile(file: String): Int
@@ -277,12 +295,52 @@ class NitUnitExecutor
                var nitc = toolcontext.find_nitc
                var opts = new Array[String]
                if mmodule != null then
+                       # FIXME playing this way with the include dir is not safe nor robust
                        opts.add "-I {mmodule.filepath.dirname}"
                end
                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
+
+       # Compile a unit file and return the compiler return code
+       #
+       # Can terminate the program if the compiler is not found
+       private fun compile_single_docunits(dus: Array[DocUnit]): Int
+       do
+               # Generate all unitfiles
+               var files = new Array[String]
+               for du in dus do
+                       files.add generate_single_docunit(du)
+               end
+
+               if files.is_empty then return 0
+
+               toolcontext.info("Compile {dus.length} single(s) doc-unit(s) at once", 1)
+
+               # Mass compile them
+               var nitc = toolcontext.find_nitc
+               var opts = new Array[String]
+               if mmodule != null then
+                       # FIXME playing this way with the include dir is not safe nor robust
+                       opts.add "-I {mmodule.filepath.dirname}"
+               end
+               var cmd = "{nitc} --ignore-visibility --no-color -q '{files.join("' '")}' {opts.join(" ")} > '{prefix}.out1' 2>&1 </dev/null --dir {prefix.dirname}"
+               var res = toolcontext.safe_exec(cmd)
+               if res != 0 then
+                       # Mass compilation failure
+                       return res
+               end
+
+               # Rename each file into it expected binary name
+               for du in dus do
+                       var f = du.test_file.as(not null)
+                       toolcontext.safe_exec("mv '{f.strip_extension(".nit")}' '{f}.bin'")
+                       du.is_compiled = true
+               end
+
+               return res
+       end
 end
 
 private class NitunitDecorator
@@ -403,6 +461,9 @@ class DocUnit
        # See `test_arg` that is used to distinguish them
        var test_file: nullable String = null
 
+       #  Was `test_file` successfully compiled?
+       var is_compiled = false
+
        # The command-line argument to use when executing the test, if any.
        var test_arg: nullable Int = null