X-Git-Url: http://nitlanguage.org diff --git a/src/testing/testing_suite.nit b/src/testing/testing_suite.nit index acf5650..f102c6c 100644 --- a/src/testing/testing_suite.nit +++ b/src/testing/testing_suite.nit @@ -17,12 +17,13 @@ module testing_suite import testing_base import html +private import annotation redef class ToolContext - # -- target-file - var opt_file = new OptionString("Specify test suite location.", "-t", "--target-file") # --pattern - var opt_pattern = new OptionString("Only run test case with name that match pattern. Examples: 'TestFoo', 'TestFoo*', 'TestFoo::test_foo', 'TestFoo::test_foo*', 'test_foo', 'test_foo*'", "-p", "--pattern") + var opt_pattern = new OptionString("Only run test case with name that match pattern", "-p", "--pattern") + # --autosav + var opt_autosav = new OptionBool("Automatically create/update .res files for black box testing", "--autosav") end # Used to test nitunit test files. @@ -31,20 +32,9 @@ class NitUnitTester # `ModelBuilder` used to parse test files. var mbuilder: ModelBuilder - # Parse a file and return the contained `MModule`. - private fun parse_module_unit(file: String): nullable MModule do - var mmodule = mbuilder.parse([file]).first - if mbuilder.get_mmodule_annotation("test_suite", mmodule) == null then return null - mbuilder.run_phases - return mmodule - end - - # Compile and execute the test suite for a NitUnit `file`. - fun test_module_unit(file: String): nullable TestSuite do + # Compile and execute `mmodule` as a test suite. + fun test_module_unit(mmodule: MModule): TestSuite do var toolcontext = mbuilder.toolcontext - var mmodule = parse_module_unit(file) - # is the module a test_suite? - if mmodule == null then return null var suite = new TestSuite(mmodule, toolcontext) # method to execute before all tests in the module var before_module = mmodule.before_test @@ -57,15 +47,11 @@ class NitUnitTester if not mclassdef.is_test then continue if not suite_match_pattern(mclassdef) then continue toolcontext.modelbuilder.total_classes += 1 - var before_test = mclassdef.before_test - var after_test = mclassdef.after_test for mpropdef in mclassdef.mpropdefs do if not mpropdef isa MMethodDef or not mpropdef.is_test then continue if not case_match_pattern(mpropdef) then continue toolcontext.modelbuilder.total_tests += 1 var test = new TestCase(suite, mpropdef, toolcontext) - test.before_test = before_test - test.after_test = after_test suite.add_test test end end @@ -135,24 +121,46 @@ class TestSuite # Test to be executed after the whole test suite. var after_module: nullable TestCase = null + fun show_status + do + toolcontext.show_unit_status("Test-suite of module " + mmodule.full_name, test_cases) + end + # Execute the test suite fun run do + show_status if not toolcontext.test_dir.file_exists then toolcontext.test_dir.mkdir end + write_to_nit + compile toolcontext.info("Execute test-suite {mmodule.name}", 1) var before_module = self.before_module - if not before_module == null then run_case(before_module) - for case in test_cases do run_case(case) + if not before_module == null then before_module.run + for case in test_cases do + case.run + toolcontext.clear_progress_bar + toolcontext.show_unit(case) + show_status + end + var after_module = self.after_module - if not after_module == null then run_case(after_module) + if not after_module == null then after_module.run + + show_status + print "" end - # Execute a test case - fun run_case(test_case: TestCase) do - test_case.write_to_nit - test_case.compile - test_case.run + # Write the test unit for `self` in a nit compilable file. + fun write_to_nit do + var file = new Template + file.addn "intrude import test_suite" + file.addn "import {mmodule.name}\n" + file.addn "var name = args.first" + for case in test_cases do + case.write_to_nit(file) + end + file.write_to_file("{test_file}.nit") end # Return the test suite in XML format compatible with Jenkins. @@ -160,13 +168,57 @@ class TestSuite fun to_xml: HTMLTag do var n = new HTMLTag("testsuite") n.attr("package", mmodule.name) - for test in test_cases do n.add test.to_xml + var failure = self.failure + if failure != null then + var f = new HTMLTag("failure") + f.attr("message", failure.to_s) + n.add f + else + for test in test_cases do n.add test.to_xml + end return n end + + # Generated test file name. + fun test_file: String do + return toolcontext.test_dir / "gen_{mmodule.name.escape_to_c}" + end + + # Compile all `test_cases` cases in one file. + fun compile do + # find nitc + var nitc = toolcontext.find_nitc + # compile test suite + var file = test_file + var module_file = mmodule.location.file + if module_file == null then + toolcontext.error(null, "Error: cannot find module file for {mmodule.name}.") + toolcontext.check_errors + return + end + var include_dir = module_file.filename.dirname + var cmd = "{nitc} --no-color -q '{file}.nit' -I {include_dir} -o '{file}.bin' > '{file}.out' 2>&1 '{file}.out1' 2>&1 '{res_name}.out1' 2>&1 '{res_name}.diff' 2>&1 1 then + toolcontext.info("Conflicting diffs: {savs.join(", ")}", 1) + error = "Conflicting expected output: {savs.join(", ", " and ")} all exist" + toolcontext.modelbuilder.failed_tests += 1 + else if not raw_output.is_empty then + toolcontext.info("No diff: {tries.join(", ", " or ")} not found", 1) + if toolcontext.opt_autosav.value then + var sav = tries.first + sav.dirname.mkdir + raw_output.write_to_file(sav) + info = "Expected output saved: {sav} (--autoupdate)" + end + end + end end - toolcontext.check_errors + is_done = true end - # Error occured during execution. - var error: nullable String = null - - # Error occured during compilation. - var failure: nullable String = null - - # Was the test case executed at least one? - var was_exec = false - - # Return the `TestCase` in XML format compatible with Jenkins. - fun to_xml: HTMLTag do + redef fun xml_classname do var mclassdef = test_method.mclassdef - var tc = new HTMLTag("testcase") - # NOTE: jenkins expects a '.' in the classname attr - tc.attr("classname", mclassdef.mmodule.full_name + "." + mclassdef.mclass.full_name) - tc.attr("name", test_method.mproperty.full_name) - if was_exec then - tc.add new HTMLTag("system-err") - var n = new HTMLTag("system-out") - n.append "out" - tc.add n - if error != null then - n = new HTMLTag("error") - n.attr("message", error.to_s) - tc.add n - end - if failure != null then - n = new HTMLTag("failure") - n.attr("message", failure.to_s) - tc.add n - end - end - return tc + return "nitunit." + mclassdef.mmodule.full_name + "." + mclassdef.mclass.full_name + end + + redef fun xml_name do + return test_method.mproperty.full_name end end @@ -300,12 +322,6 @@ redef class MMethodDef # i.e. begins with "test_" private fun is_test: Bool do return name.has_prefix("test_") - # Is the method a "before_test"? - private fun is_before: Bool do return name == "before_test" - - # Is the method a "after_test"? - private fun is_after: Bool do return name == "after_test" - # Is the method a "before_module"? private fun is_before_module: Bool do return mproperty.is_toplevel and name == "before_module" @@ -315,29 +331,15 @@ end redef class MClassDef # Is the class a TestClass? - # i.e. begins with "Test" + # i.e. is a subclass of `TestSuite` private fun is_test: Bool do + var in_hierarchy = self.in_hierarchy + if in_hierarchy == null then return false for sup in in_hierarchy.greaters do if sup.name == "TestSuite" then return true end return false end - - # "before_test" method for this classdef. - private fun before_test: nullable MMethodDef do - for mpropdef in mpropdefs do - if mpropdef isa MMethodDef and mpropdef.is_before then return mpropdef - end - return null - end - - # "after_test" method for this classdef. - private fun after_test: nullable MMethodDef do - for mpropdef in mpropdefs do - if mpropdef isa MMethodDef and mpropdef.is_after then return mpropdef - end - return null - end end redef class MModule @@ -365,32 +367,23 @@ redef class MModule end redef class ModelBuilder + # Number of test classes generated. var total_classes = 0 + + # Number of tests generated. var total_tests = 0 + + # Number of failed tests. var failed_tests = 0 - # Run NitUnit test file for mmodule (if exists). - fun test_unit(mmodule: MModule): HTMLTag do - var ts = new HTMLTag("testsuite") - toolcontext.info("nitunit: test-suite test_{mmodule}", 2) - var f = toolcontext.opt_file.value - var test_file = "test_{mmodule.name}.nit" - if f != null then - test_file = f - else if not test_file.file_exists then - var include_dir = mmodule.location.file.filename.dirname - test_file = "{include_dir}/{test_file}" - end - if not test_file.file_exists then - toolcontext.info("Skip test for {mmodule}, no file {test_file} found", 1) - return ts - end + # Run NitUnit test suite for `mmodule` (if it is one). + fun test_unit(mmodule: MModule): nullable HTMLTag do + # is the module a test_suite? + if get_mmodule_annotation("test_suite", mmodule) == null then return null + toolcontext.info("nitunit: test-suite {mmodule}", 2) + var tester = new NitUnitTester(self) - var res = tester.test_module_unit(test_file) - if res == null then - toolcontext.info("Skip test for {mmodule}, no test suite found", 1) - return ts - end + var res = tester.test_module_unit(mmodule) return res.to_xml end end