Merge: Nitunit: mass compile non-simple docunits
[nit.git] / src / testing / testing_base.nit
index 7db143e..e035c9e 100644 (file)
@@ -18,6 +18,7 @@ module testing_base
 import modelize
 private import parser_util
 import html
+import console
 
 redef class ToolContext
        # opt --full
@@ -30,11 +31,13 @@ redef class ToolContext
        var opt_noact = new OptionBool("Does not compile and run tests", "--no-act")
        # opt --nitc
        var opt_nitc = new OptionString("nitc compiler to use", "--nitc")
+       # opt --no-time
+       var opt_no_time = new OptionBool("Disable time information in XML", "--no-time")
 
        # Working directory for testing.
        fun test_dir: String do
                var dir = opt_dir.value
-               if dir == null then return ".nitunit"
+               if dir == null then return "nitunit.out"
                return dir
        end
 
@@ -61,8 +64,8 @@ redef class ToolContext
                        return nitc
                end
 
-               var nit_dir = nit_dir
-               nitc = nit_dir/"bin/nitc"
+               var nit_dir = nit_dir or else "."
+               nitc = nit_dir / "bin/nitc"
                if not nitc.file_exists then
                        fatal_error(null, "Error: cannot find nitc. Set envvar NIT_DIR or NITC or use the --nitc option.")
                        abort
@@ -93,6 +96,83 @@ ulimit -t {{{ulimit_usertime}}} 2> /dev/null
        #
        # Default: 10 CPU minute
        var ulimit_usertime = 600 is writable
+
+       # Show a single-line status to use as a progression.
+       #
+       # If `has_progress_bar` is true, then the output is a progress bar.
+       # The printed the line starts with `'\r'` and is not ended by a `'\n'`.
+       # So it is expected that:
+       # * no other output is printed between two calls
+       # * the last `show_unit_status` is followed by a new-line
+       #
+       # If `has_progress_bar` is false, then only the first and last state is shown
+       fun show_unit_status(name: String, tests: SequenceRead[UnitTest])
+       do
+               var line = "\r\x1B[K==== {name} ["
+               var done = tests.length
+               var fails = 0
+               for t in tests do
+                       if not t.is_done then
+                               line += " "
+                               done -= 1
+                       else if t.error == null then
+                               line += ".".green.bold
+                       else
+                               line += "X".red.bold
+                               fails += 1
+                       end
+               end
+
+               if not has_progress_bar then
+                       if done == 0 then
+                               print "==== {name} | tests: {tests.length}"
+                       end
+                       return
+               end
+
+               if done < tests.length then
+                       line += "] {done}/{tests.length}"
+               else
+                       line += "] tests: {tests.length} "
+                       if fails == 0 then
+                               line += "OK".green.bold
+                       else
+                               line += "KO: {fails}".red.bold
+                       end
+               end
+               printn "{line}"
+       end
+
+       # Is a progress bar printed?
+       #
+       # true if color (because and non-verbose mode
+       # (because verbose mode messes up with the progress bar).
+       fun has_progress_bar: Bool
+       do
+               return not opt_no_color.value and opt_verbose.value <= 0
+       end
+
+       # Clear the line if `has_progress_bar` (no-op else)
+       fun clear_progress_bar
+       do
+               if has_progress_bar then printn "\r\x1B[K"
+       end
+
+       # Show the full description of the test-case.
+       #
+       # The output honors `--no-color`.
+       #
+       # `more message`, if any, is added after the error message.
+       fun show_unit(test: UnitTest, more_message: nullable String) do
+               print test.to_screen(more_message, not opt_no_color.value)
+       end
+
+       # Set the `NIT_TESTING_PATH` environment variable with `path`.
+       #
+       # If `path == null` then `NIT_TESTING_PATH` is set with the empty string.
+       fun set_testing_path(path: nullable String) do
+               "NIT_TESTING_PATH".setenv(path or else "")
+       end
 end
 
 # A unit test is an elementary test discovered, run and reported by nitunit.
@@ -109,6 +189,8 @@ abstract class UnitTest
        var is_done: Bool = false is writable
 
        # Error message occurred during test-case execution (or compilation).
+       #
+       # e.g.: `Runtime Error`
        var error: nullable String = null is writable
 
        # Was the test case executed at least once?
@@ -124,19 +206,80 @@ abstract class UnitTest
        # The location where the error occurred, if it makes sense.
        var error_location: nullable Location = null is writable
 
+       # Additional noteworthy information when a test success.
+       var info: nullable String = null
+
+       # Time for the execution, in seconds
+       var real_time: Float = 0.0 is writable
+
+       # A colorful `[OK]` or `[KO]`.
+       fun status_tag(color: nullable Bool): String do
+               color = color or else true
+               if not is_done then
+                       return "[  ]"
+               else if error != null then
+                       var res = "[KO]"
+                       if color then res = res.red.bold
+                       return res
+               else
+                       var res = "[OK]"
+                       if color then res = res.green.bold
+                       return res
+               end
+       end
+
+       # The full (color) description of the test-case.
+       #
+       # `more message`, if any, is added after the error message.
+       fun to_screen(more_message: nullable String, color: nullable Bool): String do
+               color = color or else true
+               var res
+               var error = self.error
+               if error != null then
+                       if more_message != null then error += " " + more_message
+                       var loc = error_location or else location
+                       if color then
+                               res = "{status_tag(color)} {full_name}\n     {loc.to_s.yellow}: {error}\n{loc.colored_line("1;31")}"
+                       else
+                               res = "{status_tag(color)} {full_name}\n     {loc}: {error}"
+                       end
+                       var output = self.raw_output
+                       if output != null then
+                               res += "\n     Output\n\t{output.chomp.replace("\n", "\n\t")}\n"
+                       end
+               else
+                       res = "{status_tag(color)} {full_name}"
+                       if more_message != null then res += more_message
+                       var info = self.info
+                       if info != null then
+                               res += "\n     {info}"
+                       end
+               end
+               return res
+       end
+
        # Return a `<testcase>` XML node in format compatible with Jenkins unit tests.
        fun to_xml: HTMLTag do
                var tc = new HTMLTag("testcase")
                tc.attr("classname", xml_classname)
                tc.attr("name", xml_name)
+               tc.attr("time", real_time.to_s)
+
+               var output = self.raw_output
+               if output != null then output = output.trunc(8192).filter_nonprintable
                var error = self.error
                if error != null then
+                       var node
                        if was_exec then
-                               tc.open("error").append("Runtime Error")
+                               node = tc.open("error").attr("message", error)
                        else
-                               tc.open("failure").append("Compilation Error")
+                               node = tc.open("failure").attr("message", error)
+                       end
+                       if output != null then
+                               node.append(output)
                        end
-                       tc.open("system-err").append(error.trunc(8192).filter_nonprintable)
+               else if output != null then
+                       tc.open("system-err").append(output)
                end
                return tc
        end