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 # Program to extract and execute unit tests from nit source files
18 import modelize_property
19 intrude import markdown
22 # Extractor, Executor an Reporter for the tests in a module
26 var toolcontext
: ToolContext
28 # The name of the module to import
31 # The prefix of the generated Nit source-file
34 # The XML node associated to the module
35 var testsuite
: HTMLTag
38 init(toolcontext
: ToolContext, prefix
: String, modname
: String, testsuite
: HTMLTag)
40 self.toolcontext
= toolcontext
42 self.modname
= modname
43 self.testsuite
= testsuite
46 # All blocks of code from a same `ADoc`
47 var block
= new Array[String]
49 redef fun process_code
(n
: HTMLTag, text
: String)
52 var ast
= toolcontext
.parse_something
(text
)
54 # We want executable code
55 if not (ast
isa AModule or ast
isa ABlockExpr or ast
isa AExpr) then return
57 # Search `assert` in the AST
58 var v
= new SearchAssertVisitor
60 if not v
.foundit
then return
66 # used to generate distinct names
69 # The entry point for a new `ndoc` node
70 # Fill the prepated `tc` (testcase) XTM node
71 fun extract
(ndoc
: ADoc, tc
: HTMLTag)
77 if block
.is_empty
then return
80 var file
= "{prefix}{cpt}.nit"
82 toolcontext
.info
("Execute {tc.attrs["classname"]}.{tc.attrs["name"]} in {file}", 2)
84 var dir
= file
.dirname
85 if dir
!= "" then dir
.mkdir
87 f
= new OFStream.open
(file
)
88 f
.write
("# GENERATED FILE\n")
89 f
.write
("# Example extracted from a documentation\n")
90 var modname
= self.modname
91 f
.write
("import {modname}\n")
98 var cmd
= "../bin/nitg --no-color '{file}' -I . >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
99 var res
= sys
.system
(cmd
)
102 res2
= sys
.system
("./{file}.bin >>'{file}.out1' 2>&1 </dev/null")
106 f
= new IFStream.open
("{file}.out1")
108 n2
= new HTMLTag("system-err")
113 n2
= new HTMLTag("system-out")
115 for text
in block
do n2
.append
(text
)
119 var ne
= new HTMLTag("failure")
120 ne
.attr
("message", msg
)
122 toolcontext
.warning
(ndoc
.location
, "FAILURE: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
123 else if res2
!= 0 then
124 var ne
= new HTMLTag("error")
125 ne
.attr
("message", msg
)
127 toolcontext
.warning
(ndoc
.location
, "ERROR: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
129 toolcontext
.check_errors
135 class SearchAssertVisitor
138 redef fun visit
(node
)
142 else if node
isa AAssertExpr then
151 redef class ModelBuilder
152 fun test_markdown
(mmodule
: MModule): HTMLTag
154 toolcontext
.info
("nitunit: {mmodule}", 2)
156 var d
= o
.public_owner
162 var ts
= new HTMLTag("testsuite")
163 ts
.attr
("package", mmodule
.full_name
)
165 var prefix
= toolcontext
.opt_dir
.value
166 if prefix
== null then prefix
= ".nitunit"
167 prefix
= prefix
.join_path
(mmodule
.to_s
)
168 var d2m
= new NitUnitExecutor(toolcontext
, prefix
, o
.name
, ts
)
172 if mmodule2nmodule
.has_key
(mmodule
) then
173 var nmodule
= mmodule2nmodule
[mmodule
]
175 var nmoduledecl
= nmodule
.n_moduledecl
176 if nmoduledecl
== null then break label x
177 var ndoc
= nmoduledecl
.n_doc
178 if ndoc
== null then break label x
179 tc
= new HTMLTag("testcase")
180 # NOTE: jenkins expects a '.' in the classname attr
181 tc
.attr
("classname", mmodule
.full_name
+ ".<module>")
182 tc
.attr
("name", "<module>")
183 d2m
.extract
(ndoc
, tc
)
185 for nclassdef
in nmodule
.n_classdefs
do
186 var mclassdef
= nclassdef
.mclassdef
.as(not null)
187 if nclassdef
isa AStdClassdef then
188 var ndoc
= nclassdef
.n_doc
190 tc
= new HTMLTag("testcase")
191 tc
.attr
("classname", mmodule
.full_name
+ "." + mclassdef
.mclass
.full_name
)
192 tc
.attr
("name", "<class>")
193 d2m
.extract
(ndoc
, tc
)
196 for npropdef
in nclassdef
.n_propdefs
do
197 var mpropdef
= npropdef
.mpropdef
.as(not null)
198 var ndoc
= npropdef
.n_doc
200 tc
= new HTMLTag("testcase")
201 tc
.attr
("classname", mmodule
.full_name
+ "." + mclassdef
.mclass
.full_name
)
202 tc
.attr
("name", mpropdef
.mproperty
.full_name
)
203 d2m
.extract
(ndoc
, tc
)
213 redef class ToolContext
214 var opt_full
= new OptionBool("Process also imported modules", "--full")
215 var opt_output
= new OptionString("Output name (default is 'nitunit.xml')", "-o", "--output")
216 var opt_dir
= new OptionString("Working directory (default is '.nitunit')", "--dir")
219 var toolcontext
= new ToolContext
221 toolcontext
.option_context
.add_option
(toolcontext
.opt_full
, toolcontext
.opt_output
, toolcontext
.opt_dir
)
224 toolcontext
.process_options
225 var args
= toolcontext
.option_context
.rest
226 if args
.is_empty
or toolcontext
.opt_help
.value
then
227 print
"usage: nitunit [options] file.nit..."
228 toolcontext
.option_context
.usage
232 var model
= new Model
233 var modelbuilder
= new ModelBuilder(model
, toolcontext
)
235 var mmodules
= modelbuilder
.parse
(args
)
236 modelbuilder
.run_phases
238 var page
= new HTMLTag("testsuites")
240 if toolcontext
.opt_full
.value
then mmodules
= model
.mmodules
243 page
.add modelbuilder
.test_markdown
(m
)
246 var file
= toolcontext
.opt_output
.value
247 if file
== null then file
= "nitunit.xml"