From 9e9f14b7a9407c0a50a81600c0d2ef000b1354d1 Mon Sep 17 00:00:00 2001 From: Alexandre Terrasa Date: Wed, 27 Aug 2014 02:10:11 -0400 Subject: [PATCH] nitunit: allow test-suite skeleton generation. Signed-off-by: Alexandre Terrasa --- src/nitunit.nit | 31 +++++++- src/testing/testing.nit | 1 + src/testing/testing_gen.nit | 181 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 src/testing/testing_gen.nit diff --git a/src/nitunit.nit b/src/nitunit.nit index c1464a6..7eb90bb 100644 --- a/src/nitunit.nit +++ b/src/nitunit.nit @@ -19,18 +19,47 @@ import testing var toolcontext = new ToolContext -toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir, toolcontext.opt_noact, toolcontext.opt_pattern, toolcontext.opt_file) +toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir, toolcontext.opt_noact, toolcontext.opt_pattern, toolcontext.opt_file, toolcontext.opt_gen_unit, toolcontext.opt_gen_force, toolcontext.opt_gen_private, toolcontext.opt_gen_show) toolcontext.tooldescription = "Usage: nitunit [OPTION]... ...\nExecutes the unit tests from Nit source files." toolcontext.process_options(args) var args = toolcontext.option_context.rest +if toolcontext.opt_gen_unit.value then + if toolcontext.opt_pattern.value != null then + print "Option --pattern cannot be used with --gen-suite" + exit(0) + end + if toolcontext.opt_file.value != null then + print "Option --target-file cannot be used with --gen-suite" + exit(0) + end +else + if toolcontext.opt_gen_force.value then + print "Option --force must be used with --gen-suite" + exit(0) + end + if toolcontext.opt_gen_private.value then + print "Option --private must be used with --gen-suite" + exit(0) + end + if toolcontext.opt_gen_show.value then + print "Option --only-show must be used with --gen-suite" + exit(0) + end +end + var model = new Model var modelbuilder = new ModelBuilder(model, toolcontext) var mmodules = modelbuilder.parse(args) modelbuilder.run_phases +if toolcontext.opt_gen_unit.value then + modelbuilder.gen_test_unit(mmodules.first) + exit(0) +end + var page = new HTMLTag("testsuites") if toolcontext.opt_full.value then mmodules = model.mmodules diff --git a/src/testing/testing.nit b/src/testing/testing.nit index 759e0dd..24251ae 100644 --- a/src/testing/testing.nit +++ b/src/testing/testing.nit @@ -17,3 +17,4 @@ module testing import testing_doc import testing_suite +import testing_gen diff --git a/src/testing/testing_gen.nit b/src/testing/testing_gen.nit new file mode 100644 index 0000000..19532c6 --- /dev/null +++ b/src/testing/testing_gen.nit @@ -0,0 +1,181 @@ +# 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. + +# Test Suites generation. +module testing_gen + +import testing_base +import template + +# Used to generate a nitunit test file skeleton. +class NitUnitGenerator + + var toolcontext: ToolContext + + # Generate the NitUnit test file skeleton for `mmodule` in `test_file`. + fun gen_unit(mmodule: MModule, test_file: String): Template do + var with_private = toolcontext.opt_gen_private.value + var tpl = new Template + tpl.addn "module test_{mmodule.name} is test_suite" + tpl.addn "" + tpl.addn "import test_suite" + if with_private then + tpl.addn "intrude import {mmodule.name}" + else + tpl.addn "import {mmodule.name}" + end + for mclassdef in mmodule.mclassdefs do + if mclassdef.mclass.kind != concrete_kind then continue + tpl.addn "" + tpl.addn "class Test{mclassdef.name}" + tpl.addn "\tsuper TestSuite" + for mpropdef in mclassdef.mpropdefs do + if not mpropdef isa MMethodDef then continue + var mproperty = mpropdef.mproperty + if mpropdef.is_abstract then continue + if mproperty.is_init then continue + if not with_private and mproperty.visibility <= protected_visibility then continue + var case_name = case_name(mpropdef) + tpl.addn "" + tpl.addn "\tfun {case_name} do" + tpl.addn "\t\tassert not_implemented: false # TODO remove once implemented" + tpl.addn "" + tpl.addn gen_init(mclassdef) + var args = new Array[String] + for mparameter in mpropdef.msignature.mparameters do + tpl.addn gen_decl(mparameter.name, mparameter.mtype, mclassdef) + args.add mparameter.name + end + var return_mtype = mpropdef.msignature.return_mtype + if return_mtype != null then + tpl.addn gen_decl("exp", return_mtype, mclassdef) + tpl.add "\t\tvar res = " + else + tpl.add "\t\t" + end + tpl.addn gen_call(mpropdef, args) + if return_mtype != null then + tpl.addn "\t\tassert exp == res" + end + tpl.addn "\tend" + end + tpl.addn "end" + end + return tpl + end + + # Generate case name based on `MMethodDef`. + # special method name like "[]", "+"... are filtered + private fun case_name(mmethoddef: MMethodDef): String do + var name = mmethoddef.name + if name == "[]" then return "test_bra" + if name == "[]=" then return "test_bra_assign" + if name == "+" then return "test_plus" + if name == "-" then return "test_minus" + if name == "*" then return "test_star" + if name == "/" then return "test_slash" + if name == "%" then return "test_percent" + if name == "unary -" then return "test_unary_minus" + if name == "==" then return "test_equals" + if name == "!=" then return "test_not_equals" + if name == "<" then return "test_lt" + if name == "<=" then return "test_le" + if name == "<=>" then return "test_compare" + if name == ">=" then return "test_ge" + if name == ">" then return "test_gt" + return "test_{name}" + end + + # Method names that do not need a "." in call. + var nodot: Array[String] = ["+", "-", "*", "/", "%", "==", "!=", "<", "<=", "<=>", ">", ">=", ">"] + + # Generate subject init. + private fun gen_init(mclassdef: MClassDef): Streamable do + if mclassdef.mclass.arity == 0 then + return "\t\tvar subject: {mclassdef.name}" + end + return "\t\tvar subject: {mclassdef.name}[{mclassdef.bound_mtype.arguments.join(", ")}]" + end + + private fun gen_decl(name: String, mtype: MType, mclassdef: MClassDef): String do + if mtype.need_anchor then + mtype = mtype.anchor_to(mclassdef.mmodule, mclassdef.bound_mtype) + end + return "\t\tvar {name}: {mtype.to_s}" + end + + # Generate call to `method` using `args`. + private fun gen_call(method: MMethodDef, args: Array[String]): Streamable do + # Here we handle the magic of the Nit syntax, have fun :) + var name = method.name + if name == "[]" then return "subject[{args.join(", ")}]" + if name == "[]=" then + var last = args.pop + return "subject[{args.join(", ")}] = {last}" + end + if name == "unary -" then return "-subject" + var tpl = new Template + if nodot.has(name) then + tpl.add "subject {name}" + if args.length == 1 then + tpl.add " {args.first}" + else if args.length > 1 then + tpl.add " ({args.join(", ")})" + end + return tpl + end + if name.has_suffix("=") then + name = name.substring(0, name.length - 1) + var last = args.pop + tpl.add "subject.{name}" + if not args.is_empty then + tpl.add "({args.join(", ")})" + end + tpl.add " = {last}" + return tpl + end + tpl.add "subject.{name}" + if args.length == 1 then + tpl.add " {args.first}" + else if args.length > 1 then + tpl.add "({args.join(", ")})" + end + return tpl + end +end + +redef class ModelBuilder + # Generate NitUnit test file skeleton for `mmodule`. + fun gen_test_unit(mmodule: MModule) do + var test_file = "test_{mmodule.name}.nit" + if test_file.file_exists and not toolcontext.opt_gen_force.value and not toolcontext.opt_gen_show.value then + toolcontext.info("Skip generation for {mmodule}, file {test_file} already exists", 1) + return + end + var generator = new NitUnitGenerator(toolcontext) + var tpl = generator.gen_unit(mmodule, test_file) + if toolcontext.opt_gen_show.value then + tpl.write_to(sys.stdout) + else + tpl.write_to_file(test_file) + end + end +end + +redef class ToolContext + var opt_gen_unit = new OptionBool("Generate test suite skeleton for a module", "--gen-suite") + var opt_gen_force = new OptionBool("Force test generation even if file exists", "-f", "--force") + var opt_gen_private = new OptionBool("Also generate test case for private methods", "--private") + var opt_gen_show = new OptionBool("Only display skeleton, do not write file", "--only-show") +end -- 1.7.9.5