1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Test Suites generation.
21 # Used to generate a nitunit test file skeleton.
22 class NitUnitGenerator
24 var toolcontext
: ToolContext
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"
33 tpl
.addn
"intrude import {mmodule.name}"
35 tpl
.addn
"import {mmodule.name}"
37 for mclassdef
in mmodule
.mclassdefs
do
38 if mclassdef
.mclass
.kind
!= concrete_kind
then continue
40 tpl
.addn
"class Test{mclassdef.name}"
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
)
50 tpl
.addn
"\tfun {case_name} is test do"
51 tpl
.addn
"\t\tassert not_implemented: false # TODO remove once implemented"
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
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 = "
66 tpl
.addn gen_call
(mpropdef
, args
)
67 if return_mtype
!= null then
68 tpl
.addn
"\t\tassert exp == res"
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"
99 # Method names that do not need a "." in call.
100 var nodot
: Array[String] = ["+", "-", "*", "/", "%", "==", "!=", "<", "<=", "<=>", ">", ">=", ">"]
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}"
107 return "\t\tvar subject: {mclassdef.name}[{mclassdef.bound_mtype.arguments.join(", ")}]"
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
)
114 return "\t\tvar {name}: {mtype.to_s}"
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
124 return "subject[{args.join(", ")}] = {last}"
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(", ")})"
137 if name
.has_suffix
("=") then
138 name
= name
.substring
(0, name
.length
- 1)
140 tpl
.add
"subject.{name}"
141 if not args
.is_empty
then
142 tpl
.add
"({args.join(", ")})"
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(", ")})"
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)
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
)
170 tpl
.write_to_file
(test_file
)
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")