Merge: doc: fixed some typos and other misc. corrections
[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"
31 tpl.addn ""
32 if with_private then
33 tpl.addn "intrude import {mmodule.name}"
34 else
35 tpl.addn "import {mmodule.name}"
36 end
37 for mclassdef in mmodule.mclassdefs do
38 if mclassdef.mclass.kind != concrete_kind then continue
39 tpl.addn ""
40 tpl.addn "class Test{mclassdef.name}"
41 tpl.addn "\ttest"
42 for mpropdef in mclassdef.mpropdefs do
43 if not mpropdef isa MMethodDef then continue
44 var mproperty = mpropdef.mproperty
45 if mpropdef.is_abstract then continue
46 if mproperty.is_init then continue
47 if not with_private and mproperty.visibility <= protected_visibility then continue
48 var case_name = case_name(mpropdef)
49 tpl.addn ""
50 tpl.addn "\tfun {case_name} is test do"
51 tpl.addn "\t\tassert not_implemented: false # TODO remove once implemented"
52 tpl.addn ""
53 tpl.addn gen_init(mclassdef)
54 var args = new Array[String]
55 for mparameter in mpropdef.msignature.mparameters do
56 tpl.addn gen_decl(mparameter.name, mparameter.mtype, mclassdef)
57 args.add mparameter.name
58 end
59 var return_mtype = mpropdef.msignature.return_mtype
60 if return_mtype != null then
61 tpl.addn gen_decl("exp", return_mtype, mclassdef)
62 tpl.add "\t\tvar res = "
63 else
64 tpl.add "\t\t"
65 end
66 tpl.addn gen_call(mpropdef, args)
67 if return_mtype != null then
68 tpl.addn "\t\tassert exp == res"
69 end
70 tpl.addn "\tend"
71 end
72 tpl.addn "end"
73 end
74 return tpl
75 end
76
77 # Generate case name based on `MMethodDef`.
78 # special method name like "[]", "+"... are filtered
79 private fun case_name(mmethoddef: MMethodDef): String do
80 var name = mmethoddef.name
81 if name == "[]" then return "test_bra"
82 if name == "[]=" then return "test_bra_assign"
83 if name == "+" then return "test_plus"
84 if name == "-" then return "test_minus"
85 if name == "*" then return "test_star"
86 if name == "/" then return "test_slash"
87 if name == "%" then return "test_percent"
88 if name == "unary -" then return "test_unary_minus"
89 if name == "==" then return "test_equals"
90 if name == "!=" then return "test_not_equals"
91 if name == "<" then return "test_lt"
92 if name == "<=" then return "test_le"
93 if name == "<=>" then return "test_compare"
94 if name == ">=" then return "test_ge"
95 if name == ">" then return "test_gt"
96 return "test_{name}"
97 end
98
99 # Method names that do not need a "." in call.
100 var nodot: Array[String] = ["+", "-", "*", "/", "%", "==", "!=", "<", "<=", "<=>", ">", ">=", ">"]
101
102 # Generate subject init.
103 private fun gen_init(mclassdef: MClassDef): Writable do
104 if mclassdef.mclass.arity == 0 then
105 return "\t\tvar subject: {mclassdef.name}"
106 end
107 return "\t\tvar subject: {mclassdef.name}[{mclassdef.bound_mtype.arguments.join(", ")}]"
108 end
109
110 private fun gen_decl(name: String, mtype: MType, mclassdef: MClassDef): String do
111 if mtype.need_anchor then
112 mtype = mtype.anchor_to(mclassdef.mmodule, mclassdef.bound_mtype)
113 end
114 return "\t\tvar {name}: {mtype.to_s}"
115 end
116
117 # Generate call to `method` using `args`.
118 private fun gen_call(method: MMethodDef, args: Array[String]): Writable do
119 # Here we handle the magic of the Nit syntax, have fun :)
120 var name = method.name
121 if name == "[]" then return "subject[{args.join(", ")}]"
122 if name == "[]=" then
123 var last = args.pop
124 return "subject[{args.join(", ")}] = {last}"
125 end
126 if name == "unary -" then return "-subject"
127 var tpl = new Template
128 if nodot.has(name) then
129 tpl.add "subject {name}"
130 if args.length == 1 then
131 tpl.add " {args.first}"
132 else if args.length > 1 then
133 tpl.add " ({args.join(", ")})"
134 end
135 return tpl
136 end
137 if name.has_suffix("=") then
138 name = name.substring(0, name.length - 1)
139 var last = args.pop
140 tpl.add "subject.{name}"
141 if not args.is_empty then
142 tpl.add "({args.join(", ")})"
143 end
144 tpl.add " = {last}"
145 return tpl
146 end
147 tpl.add "subject.{name}"
148 if args.length == 1 then
149 tpl.add " {args.first}"
150 else if args.length > 1 then
151 tpl.add "({args.join(", ")})"
152 end
153 return tpl
154 end
155 end
156
157 redef class ModelBuilder
158 # Generate NitUnit test file skeleton for `mmodule`.
159 fun gen_test_unit(mmodule: MModule) do
160 var test_file = "test_{mmodule.name}.nit"
161 if test_file.file_exists and not toolcontext.opt_gen_force.value and not toolcontext.opt_gen_show.value then
162 toolcontext.info("Skip generation for {mmodule}, file {test_file} already exists", 1)
163 return
164 end
165 var generator = new NitUnitGenerator(toolcontext)
166 var tpl = generator.gen_unit(mmodule, test_file)
167 if toolcontext.opt_gen_show.value then
168 tpl.write_to(sys.stdout)
169 else
170 tpl.write_to_file(test_file)
171 end
172 end
173 end
174
175 redef class ToolContext
176 var opt_gen_unit = new OptionBool("Generate test suite skeleton for a module", "--gen-suite")
177 var opt_gen_force = new OptionBool("Force test generation even if file exists", "-f", "--force")
178 var opt_gen_private = new OptionBool("Also generate test case for private methods", "--private")
179 var opt_gen_show = new OptionBool("Only display the skeleton, do not write any file", "--only-show")
180 end