Rename REAMDE to README.md
[nit.git] / src / testing / testing_gen.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Test Suites generation.
16 module testing_gen
17
18 import testing_base
19 import template
20
21 # Used to generate a nitunit test file skeleton.
22 class NitUnitGenerator
23
24 var toolcontext: ToolContext
25
26 # Generate the NitUnit test file skeleton for `mmodule` in `test_file`.
27 fun gen_unit(mmodule: MModule, test_file: String): Template do
28 var with_private = toolcontext.opt_gen_private.value
29 var tpl = new Template
30 tpl.addn "module test_{mmodule.name} is test_suite"
31 tpl.addn ""
32 tpl.addn "import test_suite"
33 if with_private then
34 tpl.addn "intrude import {mmodule.name}"
35 else
36 tpl.addn "import {mmodule.name}"
37 end
38 for mclassdef in mmodule.mclassdefs do
39 if mclassdef.mclass.kind != concrete_kind then continue
40 tpl.addn ""
41 tpl.addn "class Test{mclassdef.name}"
42 tpl.addn "\tsuper TestSuite"
43 for mpropdef in mclassdef.mpropdefs do
44 if not mpropdef isa MMethodDef then continue
45 var mproperty = mpropdef.mproperty
46 if mpropdef.is_abstract then continue
47 if mproperty.is_init then continue
48 if not with_private and mproperty.visibility <= protected_visibility then continue
49 var case_name = case_name(mpropdef)
50 tpl.addn ""
51 tpl.addn "\tfun {case_name} do"
52 tpl.addn "\t\tassert not_implemented: false # TODO remove once implemented"
53 tpl.addn ""
54 tpl.addn gen_init(mclassdef)
55 var args = new Array[String]
56 for mparameter in mpropdef.msignature.mparameters do
57 tpl.addn gen_decl(mparameter.name, mparameter.mtype, mclassdef)
58 args.add mparameter.name
59 end
60 var return_mtype = mpropdef.msignature.return_mtype
61 if return_mtype != null then
62 tpl.addn gen_decl("exp", return_mtype, mclassdef)
63 tpl.add "\t\tvar res = "
64 else
65 tpl.add "\t\t"
66 end
67 tpl.addn gen_call(mpropdef, args)
68 if return_mtype != null then
69 tpl.addn "\t\tassert exp == res"
70 end
71 tpl.addn "\tend"
72 end
73 tpl.addn "end"
74 end
75 return tpl
76 end
77
78 # Generate case name based on `MMethodDef`.
79 # special method name like "[]", "+"... are filtered
80 private fun case_name(mmethoddef: MMethodDef): String do
81 var name = mmethoddef.name
82 if name == "[]" then return "test_bra"
83 if name == "[]=" then return "test_bra_assign"
84 if name == "+" then return "test_plus"
85 if name == "-" then return "test_minus"
86 if name == "*" then return "test_star"
87 if name == "/" then return "test_slash"
88 if name == "%" then return "test_percent"
89 if name == "unary -" then return "test_unary_minus"
90 if name == "==" then return "test_equals"
91 if name == "!=" then return "test_not_equals"
92 if name == "<" then return "test_lt"
93 if name == "<=" then return "test_le"
94 if name == "<=>" then return "test_compare"
95 if name == ">=" then return "test_ge"
96 if name == ">" then return "test_gt"
97 return "test_{name}"
98 end
99
100 # Method names that do not need a "." in call.
101 var nodot: Array[String] = ["+", "-", "*", "/", "%", "==", "!=", "<", "<=", "<=>", ">", ">=", ">"]
102
103 # Generate subject init.
104 private fun gen_init(mclassdef: MClassDef): Writable do
105 if mclassdef.mclass.arity == 0 then
106 return "\t\tvar subject: {mclassdef.name}"
107 end
108 return "\t\tvar subject: {mclassdef.name}[{mclassdef.bound_mtype.arguments.join(", ")}]"
109 end
110
111 private fun gen_decl(name: String, mtype: MType, mclassdef: MClassDef): String do
112 if mtype.need_anchor then
113 mtype = mtype.anchor_to(mclassdef.mmodule, mclassdef.bound_mtype)
114 end
115 return "\t\tvar {name}: {mtype.to_s}"
116 end
117
118 # Generate call to `method` using `args`.
119 private fun gen_call(method: MMethodDef, args: Array[String]): Writable do
120 # Here we handle the magic of the Nit syntax, have fun :)
121 var name = method.name
122 if name == "[]" then return "subject[{args.join(", ")}]"
123 if name == "[]=" then
124 var last = args.pop
125 return "subject[{args.join(", ")}] = {last}"
126 end
127 if name == "unary -" then return "-subject"
128 var tpl = new Template
129 if nodot.has(name) then
130 tpl.add "subject {name}"
131 if args.length == 1 then
132 tpl.add " {args.first}"
133 else if args.length > 1 then
134 tpl.add " ({args.join(", ")})"
135 end
136 return tpl
137 end
138 if name.has_suffix("=") then
139 name = name.substring(0, name.length - 1)
140 var last = args.pop
141 tpl.add "subject.{name}"
142 if not args.is_empty then
143 tpl.add "({args.join(", ")})"
144 end
145 tpl.add " = {last}"
146 return tpl
147 end
148 tpl.add "subject.{name}"
149 if args.length == 1 then
150 tpl.add " {args.first}"
151 else if args.length > 1 then
152 tpl.add "({args.join(", ")})"
153 end
154 return tpl
155 end
156 end
157
158 redef class ModelBuilder
159 # Generate NitUnit test file skeleton for `mmodule`.
160 fun gen_test_unit(mmodule: MModule) do
161 var test_file = "test_{mmodule.name}.nit"
162 if test_file.file_exists and not toolcontext.opt_gen_force.value and not toolcontext.opt_gen_show.value then
163 toolcontext.info("Skip generation for {mmodule}, file {test_file} already exists", 1)
164 return
165 end
166 var generator = new NitUnitGenerator(toolcontext)
167 var tpl = generator.gen_unit(mmodule, test_file)
168 if toolcontext.opt_gen_show.value then
169 tpl.write_to(sys.stdout)
170 else
171 tpl.write_to_file(test_file)
172 end
173 end
174 end
175
176 redef class ToolContext
177 var opt_gen_unit = new OptionBool("Generate test suite skeleton for a module", "--gen-suite")
178 var opt_gen_force = new OptionBool("Force test generation even if file exists", "-f", "--force")
179 var opt_gen_private = new OptionBool("Also generate test case for private methods", "--private")
180 var opt_gen_show = new OptionBool("Only display skeleton, do not write file", "--only-show")
181 end