# This file is part of NIT ( http://www.nitlanguage.org ). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Testing from external files. 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") end # Used to test nitunit test files. 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 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 if before_module != null then toolcontext.modelbuilder.total_tests += 1 suite.before_module = new TestCase(suite, before_module, toolcontext) end # generate all test cases for mclassdef in mmodule.mclassdefs do 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 # method to execute after all tests in the module var after_module = mmodule.after_test if after_module != null then toolcontext.modelbuilder.total_tests += 1 suite.after_module = new TestCase(suite, after_module, toolcontext) end suite.run return suite end # Is the test suite name match the pattern option? private fun suite_match_pattern(suite: MClassDef): Bool do var pattern = mbuilder.toolcontext.opt_pattern.value if pattern == null then return true var ps = pattern.split_with("::") var p = ps.first if ps.length == 1 and p.first.is_lower then return true if ps.length == 2 and p.first.is_lower then return false if p.has_suffix("*") then p = p.substring(0, p.length - 1) if suite.name.has_prefix(p) then return true else if suite.name == p then return true end return false end # Is the test case name match the pattern option? private fun case_match_pattern(case: MPropDef): Bool do var pattern = mbuilder.toolcontext.opt_pattern.value if pattern == null then return true var ps = pattern.split_with("::") var p = ps.last if ps.length == 1 and p.first.is_upper then return true if ps.length == 2 and p.first.is_upper then return false if p.has_suffix("*") then p = p.substring(0, p.length - 1) if case.name.has_prefix(p) then return true else if case.name == p then return true end return false end end # A test suite contains all the test cases for a `MModule`. class TestSuite # `MModule` under test var mmodule: MModule # `ToolContext` to use to display messages. var toolcontext: ToolContext # List of `TestCase` to be executed in this suite. var test_cases = new Array[TestCase] # Add a `TestCase` to the suite. fun add_test(case: TestCase) do test_cases.add case # Test to be executed before the whole test suite. var before_module: nullable TestCase = null # Test to be executed after the whole test suite. var after_module: nullable TestCase = null # Execute the test suite fun run do if not toolcontext.test_dir.file_exists then toolcontext.test_dir.mkdir end 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) var after_module = self.after_module if not after_module == null then run_case(after_module) end # Execute a test case fun run_case(test_case: TestCase) do test_case.write_to_nit test_case.compile test_case.run end # Return the test suite in XML format compatible with Jenkins. # Contents depends on the `run` execution. 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 return n end end # A test case is a unit test considering only a `MMethodDef`. class TestCase # Test suite wich `self` belongs to. var test_suite: TestSuite # Test method to be compiled and tested. var test_method: MMethodDef # `ToolContext` to use to display messages and find `nitg` bin. var toolcontext: ToolContext # `MMethodDef` to call before the test case. var before_test: nullable MMethodDef = null # `MMethodDef` to call after the test case. var after_test: nullable MMethodDef = null # Generated test file name. fun test_file: String do var dir = toolcontext.test_dir var mod = test_method.mclassdef.mmodule.name var cls = test_method.mclassdef.name var name = test_method.name return "{dir}/{mod}_{cls}_{name}" end # Generate the test unit in a nit file. fun write_to_nit do var name = test_method.name var file = new Template file.addn "intrude import test_suite" file.addn "import {test_method.mclassdef.mmodule.name}\n" if test_method.mproperty.is_toplevel then file.addn name else file.addn "var subject = new {test_method.mclassdef.name}.nitunit" if before_test != null then file.addn "subject.{before_test.name}" file.addn "subject.{name}" if after_test != null then file.addn "subject.{after_test.name}" end file.write_to_file("{test_file}.nit") end # Compile all test cases in once. fun compile do # find nitg var nit_dir = toolcontext.nit_dir var nitg = nit_dir/"bin/nitg" if not nitg.file_exists then toolcontext.error(null, "Cannot find nitg. Set envvar NIT_DIR.") toolcontext.check_errors end # compile test suite var file = test_file var include_dir = test_method.mclassdef.mmodule.location.file.filename.dirname var cmd = "{nitg} --no-color '{file}.nit' -I {include_dir} -o '{file}.bin' > '{file}.out' 2>&1 '{file}.out1' 2>&1