Merge: nitunit: improve progress display
authorJean Privat <jean@pryen.org>
Fri, 3 Jun 2016 17:17:39 +0000 (13:17 -0400)
committerJean Privat <jean@pryen.org>
Fri, 3 Jun 2016 17:17:39 +0000 (13:17 -0400)
The results are printed progressively instead of at the end of each test-suite.
The progress bar in therefore displayed below the results.

See it in action: https://asciinema.org/a/17u9jwts0nz5dfpxp8p9aab0f

Pull-Request: #2156
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

src/testing/testing_base.nit
src/testing/testing_doc.nit
src/testing/testing_suite.nit
tests/sav/nitunit_args1.res
tests/sav/nitunit_args4.res
tests/sav/nitunit_args5.res
tests/sav/nitunit_args6.res
tests/sav/nitunit_args7.res
tests/sav/nitunit_args8.res
tests/sav/nitunit_args9.res

index ddc167f..b01d7dd 100644 (file)
@@ -97,15 +97,19 @@ ulimit -t {{{ulimit_usertime}}} 2> /dev/null
 
        # Show a single-line status to use as a progression.
        #
-       # Note that the line starts with `'\r'` and is not ended by a `'\n'`.
+       # 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
-       fun show_unit_status(name: String, tests: SequenceRead[UnitTest], more_message: nullable String)
+       #
+       # 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 esc = 27.code_point.to_s
-               var line = "\r{esc}[K* {name} ["
+               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 += " "
@@ -114,24 +118,46 @@ ulimit -t {{{ulimit_usertime}}} 2> /dev/null
                                line += ".".green.bold
                        else
                                line += "X".red.bold
+                               fails += 1
                        end
                end
 
-               if opt_no_color.value then
+               if not has_progress_bar then
                        if done == 0 then
-                               print "* {name} ({tests.length} tests)"
+                               print "==== {name} | tests: {tests.length}"
                        end
                        return
                end
 
-               line += "] {done}/{tests.length}"
-               if more_message != null then
-                       line += " " + more_message
+               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
 
-       # Shoe the full description of the test-case.
+       # 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`.
        #
index a2fa067..814f7d2 100644 (file)
@@ -78,15 +78,17 @@ class NitUnitExecutor
        # All extracted docunits
        var docunits = new Array[DocUnit]
 
-       fun show_status(more_message: nullable String)
+       fun show_status
        do
-               toolcontext.show_unit_status(name, docunits, more_message)
+               toolcontext.show_unit_status(name, docunits)
        end
 
        fun mark_done(du: DocUnit)
        do
                du.is_done = true
-               show_status(du.full_name + " " + du.status_tag)
+               toolcontext.clear_progress_bar
+               toolcontext.show_unit(du)
+               show_status
        end
 
        # Execute all the docunits
@@ -96,33 +98,41 @@ class NitUnitExecutor
                        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
-                               mark_done(du)
                                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
-                       toolcontext.show_unit(du)
-               end
-
-               for du in docunits do
                        testsuite.add du.to_xml
                end
        end
@@ -164,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.was_exec = true
+                       du.test_file = file
+                       du.test_arg = i
+               end
+       end
 
-                       var content = "{file}.out1".to_path.read_all
-                       du.raw_output = content
+       # 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 = "Runtime error in {file} with argument {i}"
-                               toolcontext.modelbuilder.failed_entities += 1
-                       end
-                       mark_done(du)
+               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
 
@@ -222,7 +239,6 @@ class NitUnitExecutor
                        du.error = "Runtime error in {file}"
                        toolcontext.modelbuilder.failed_entities += 1
                end
-               mark_done(du)
        end
 
        # Create and fill the header of a unit file `file`.
@@ -349,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)
@@ -376,10 +392,23 @@ class DocUnit
        # The numbering of self in mdoc (starting with 0)
        var number: Int
 
+       # 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
+                       var res = mentity.full_name
+                       if number > 1 then
+                               res += "#{number}"
+                       end
+                       return res
                else
                        return xml_classname + "." + xml_name
                end
@@ -543,7 +572,7 @@ 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)
 
index f755d25..309393c 100644 (file)
@@ -132,9 +132,9 @@ class TestSuite
        # Test to be executed after the whole test suite.
        var after_module: nullable TestCase = null
 
-       fun show_status(more_message: nullable String)
+       fun show_status
        do
-               toolcontext.show_unit_status("Test-suite of module " + mmodule.full_name, test_cases, more_message)
+               toolcontext.show_unit_status("Test-suite of module " + mmodule.full_name, test_cases)
        end
 
        # Execute the test suite
@@ -150,17 +150,16 @@ class TestSuite
                if not before_module == null then before_module.run
                for case in test_cases do
                        case.run
-                       show_status(case.full_name + " " + case.status_tag)
+                       toolcontext.clear_progress_bar
+                       toolcontext.show_unit(case)
+                       show_status
                end
 
-               show_status
-               print ""
-
                var after_module = self.after_module
                if not after_module == null then after_module.run
-               for case in test_cases do
-                       toolcontext.show_unit(case)
-               end
+
+               show_status
+               print ""
        end
 
        # Write the test unit for `self` in a nit compilable file.
index a45c8d4..65ee58d 100644 (file)
@@ -1,5 +1,4 @@
-* Docunits of module test_nitunit::test_nitunit (4 tests)
-
+==== Docunits of module test_nitunit::test_nitunit | tests: 4
 [OK] test_nitunit::test_nitunit
 [KO] test_nitunit$X
      test_nitunit.nit:21,7--22,0: Runtime error in nitunit.out/test_nitunit-2.nit
@@ -13,8 +12,8 @@
 
 [KO] test_nitunit$X$foo1
      test_nitunit.nit:28,15: Syntax Error: unexpected operator '!'.
-* Test-suite of module test_test_nitunit::test_test_nitunit (3 tests)
 
+==== Test-suite of module test_test_nitunit::test_test_nitunit | tests: 3
 [OK] test_test_nitunit$TestX$test_foo
 [KO] test_test_nitunit$TestX$test_foo1
      test_test_nitunit.nit:36,2--40,4: Runtime Error in file nitunit.out/gen_test_test_nitunit.nit
@@ -22,6 +21,7 @@
        Runtime error: Assert failed (test_test_nitunit.nit:39)
 
 [OK] test_test_nitunit$TestX$test_foo2
+
 Docunits: Entities: 27; Documented ones: 4; With nitunits: 4; Failures: 3
 Test suites: Classes: 1; Test Cases: 3; Failures: 1
 [FAILURE] 4/7 tests failed.
index a1f4cb1..cbfb8c5 100644 (file)
@@ -1,8 +1,8 @@
-* Docunits of module test_nitunit2::test_nitunit2 (3 tests)
-
+==== Docunits of module test_nitunit2::test_nitunit2 | tests: 3
 [OK] test_nitunit2::test_nitunit2$core::Sys$foo1
 [OK] test_nitunit2::test_nitunit2$core::Sys$bar2
 [OK] test_nitunit2::test_nitunit2$core::Sys$foo3
+
 Docunits: Entities: 4; Documented ones: 3; With nitunits: 3; Failures: 0
 Test suites: Classes: 0; Test Cases: 0
 [SUCCESS] All 3 tests passed.
index 3e25819..38cb4c7 100644 (file)
@@ -1,8 +1,8 @@
-* Docunits of module test_doc2::test_doc2 (3 tests)
-
+==== Docunits of module test_doc2::test_doc2 | tests: 3
 [OK] test_doc2::test_doc2$core::Sys$foo1
 [OK] test_doc2::test_doc2$core::Sys$foo2
 [OK] test_doc2::test_doc2$core::Sys$foo3
+
 Docunits: Entities: 6; Documented ones: 5; With nitunits: 3; Failures: 0
 Test suites: Classes: 0; Test Cases: 0
 [SUCCESS] All 3 tests passed.
index 2b931af..ff7185a 100644 (file)
@@ -1,15 +1,15 @@
-* Docunits of group test_nitunit3> (2 tests)
-
+==== Docunits of group test_nitunit3> | tests: 2
 [KO] test_nitunit3>
      test_nitunit3/README.md:4,2--15,0: Runtime error in nitunit.out/test_nitunit3-0.nit with argument 1
      Output
        Runtime error: Assert failed (nitunit.out/test_nitunit3-0.nit:7)
 
-[KO] test_nitunit3>
+[KO] test_nitunit3>#2
      test_nitunit3/README.md:7,3--5: Syntax Error: unexpected malformed character '\].
-* Docunits of module test_nitunit3::test_nitunit3 (1 tests)
 
+==== Docunits of module test_nitunit3::test_nitunit3 | tests: 1
 [OK] test_nitunit3::test_nitunit3
+
 Docunits: Entities: 2; Documented ones: 2; With nitunits: 3; Failures: 2
 Test suites: Classes: 0; Test Cases: 0
 [FAILURE] 2/3 tests failed.
@@ -17,6 +17,6 @@ Test suites: Classes: 0; Test Cases: 0
 <testsuites><testsuite package="test_nitunit3&gt;"><testcase classname="nitunit.test_nitunit3&gt;" name="&lt;group&gt;"><error>Runtime error in nitunit.out&#47;test_nitunit3-0.nit with argument 1</error><system-err>Runtime error: Assert failed (nitunit.out&#47;test_nitunit3-0.nit:7)
 </system-err><system-out>assert false
 assert true
-</system-out></testcase><testcase classname="nitunit.test_nitunit3&gt;" name="&lt;group&gt;+1"><failure>Syntax Error: unexpected malformed character &#39;\].</failure><system-out>;&#39;\][]
+</system-out></testcase><testcase classname="nitunit.test_nitunit3&gt;" name="&lt;group&gt;#2"><failure>Syntax Error: unexpected malformed character &#39;\].</failure><system-out>;&#39;\][]
 </system-out></testcase></testsuite><testsuite package="test_nitunit3::test_nitunit3"><testcase classname="nitunit.test_nitunit3::test_nitunit3.&lt;module&gt;" name="&lt;module&gt;"><system-err></system-err><system-out>assert true
 </system-out></testcase></testsuite><testsuite></testsuite></testsuites>
\ No newline at end of file
index 4550bb5..3ac61a8 100644 (file)
@@ -1,15 +1,15 @@
-* Docunits of file test_nitunit_md.md:1,0--15,0 (1 tests)
-
-[KO] nitunit.<file>.test_nitunit_md.md:1,0--15,0
+==== Docunits of file test_nitunit_md.md | tests: 1
+[KO] nitunit.<file>.test_nitunit_md.md
      test_nitunit_md.md:4,2--16,0: Runtime error in nitunit.out/file-0.nit with argument 1
      Output
        Runtime error: Assert failed (nitunit.out/file-0.nit:8)
 
+
 Docunits: Entities: 1; Documented ones: 1; With nitunits: 1; Failures: 1
 Test suites: Classes: 0; Test Cases: 0
 [FAILURE] 1/1 tests failed.
 `nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit_md.md:1,0--15,0"><testcase classname="nitunit.&lt;file&gt;" name="test_nitunit_md.md:1,0--15,0"><error>Runtime error in nitunit.out&#47;file-0.nit with argument 1</error><system-err>Runtime error: Assert failed (nitunit.out&#47;file-0.nit:8)
+<testsuites><testsuite package="test_nitunit_md.md"><testcase classname="nitunit.&lt;file&gt;" name="test_nitunit_md.md"><error>Runtime error in nitunit.out&#47;file-0.nit with argument 1</error><system-err>Runtime error: Assert failed (nitunit.out&#47;file-0.nit:8)
 </system-err><system-out>var a = 1
 assert 1 == 1
 assert false
index 65fd9b3..d9fdafc 100644 (file)
@@ -1,11 +1,11 @@
-* Docunits of module test_doc3::test_doc3 (3 tests)
-
+==== Docunits of module test_doc3::test_doc3 | tests: 3
 [KO] test_doc3::test_doc3$core::Sys$foo1
      test_doc3.nit:17,9--15: Syntax Error: unexpected identifier 'garbage'.
 [KO] test_doc3::test_doc3$core::Sys$foo2
      test_doc3.nit:23,4--10: Syntax Error: unexpected identifier 'garbage'.
 [KO] test_doc3::test_doc3$core::Sys$foo3
      test_doc3.nit:30,4--10: Syntax Error: unexpected identifier 'garbage'.
+
 Docunits: Entities: 6; Documented ones: 5; With nitunits: 3; Failures: 3
 Test suites: Classes: 0; Test Cases: 0
 [FAILURE] 3/3 tests failed.
index 7aa0887..7d1f2bd 100644 (file)
@@ -1,5 +1,4 @@
-* Test-suite of module test_nitunit4::test_nitunit4 (3 tests)
-
+==== Test-suite of module test_nitunit4::test_nitunit4 | tests: 3
 [KO] test_nitunit4$TestTestSuite$test_foo
      test_nitunit4/test_nitunit4.nit:22,2--26,4: Runtime Error in file nitunit.out/gen_test_nitunit4.nit
      Output
@@ -21,6 +20,7 @@
        +Tested method
        +After Test
 
+
 Docunits: Entities: 12; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 1; Test Cases: 3; Failures: 2
 [FAILURE] 2/3 tests failed.