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 # Testing from code comments.
19 intrude import markdown
21 # Extractor, Executor and Reporter for the tests in a module
25 # The module to import
28 # The prefix of the generated Nit source-file
31 # The XML node associated to the module
32 var testsuite
: HTMLTag
35 init(toolcontext
: ToolContext, prefix
: String, mmodule
: MModule, testsuite
: HTMLTag)
39 self.mmodule
= mmodule
40 self.testsuite
= testsuite
43 # All blocks of code from a same `ADoc`
44 var blocks
= new Array[Array[String]]
46 redef fun process_code
(n
: HTMLTag, text
: String)
49 var ast
= toolcontext
.parse_something
(text
)
51 # We want executable code
52 if not (ast
isa AModule or ast
isa ABlockExpr or ast
isa AExpr) then
53 if ndoc
!= null and n
.tag
== "pre" and toolcontext
.opt_warn
.value
> 1 then
54 toolcontext
.warning
(ndoc
.location
, "invalid-block", "Warning: There is a block of code that is not valid Nit, thus not considered a nitunit")
55 if ast
isa AError then toolcontext
.warning
(ast
.location
, "syntax-error", ast
.message
)
56 ndoc
= null # To avoid multiple warning in the same node
61 # Search `assert` in the AST
62 var v
= new SearchAssertVisitor
65 if ndoc
!= null and n
.tag
== "pre" and toolcontext
.opt_warn
.value
> 1 then
66 toolcontext
.warning
(ndoc
.location
, "invalid-block", "Warning: There is a block of Nit code without `assert`, thus not considered a nitunit")
67 ndoc
= null # To avoid multiple warning in the same node
72 # Create a first block
73 # Or create a new block for modules that are more than a main part
74 if blocks
.is_empty
or ast
isa AModule then
75 blocks
.add
(new Array[String])
82 # The associated node to localize warnings
83 var ndoc
: nullable ADoc
85 # used to generate distinct names
88 # The entry point for a new `ndoc` node
89 # Fill the prepated `tc` (testcase) XTM node
90 fun extract
(ndoc
: ADoc, tc
: HTMLTag)
97 toolcontext
.check_errors
99 if blocks
.is_empty
then return
101 for block
in blocks
do test_block
(ndoc
, tc
, block
)
105 fun test_block
(ndoc
: ADoc, tc
: HTMLTag, block
: Array[String])
107 toolcontext
.modelbuilder
.unit_entities
+= 1
110 var file
= "{prefix}{cpt}.nit"
112 toolcontext
.info
("Execute doc-unit {tc.attrs["name"]} in {file}", 1)
114 var dir
= file
.dirname
115 if dir
!= "" then dir
.mkdir
117 f
= new OFStream.open
(file
)
118 f
.write
("# GENERATED FILE\n")
119 f
.write
("# Example extracted from a documentation\n")
120 f
.write
("import {mmodule.name}\n")
127 if toolcontext
.opt_noact
.value
then return
129 var nit_dir
= toolcontext
.nit_dir
130 var nitg
= "{nit_dir or else ""}/bin/nitg"
131 if nit_dir
== null or not nitg
.file_exists
then
132 toolcontext
.error
(null, "Cannot find nitg. Set envvar NIT_DIR.")
133 toolcontext
.check_errors
135 var cmd
= "{nitg} --ignore-visibility --no-color '{file}' -I {mmodule.location.file.filename.dirname} >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
136 var res
= sys
.system
(cmd
)
139 res2
= sys
.system
("./{file}.bin >>'{file}.out1' 2>&1 </dev/null")
143 f
= new IFStream.open
("{file}.out1")
145 n2
= new HTMLTag("system-err")
150 n2
= new HTMLTag("system-out")
152 for text
in block
do n2
.append
(text
)
156 var ne
= new HTMLTag("failure")
157 ne
.attr
("message", msg
)
159 toolcontext
.warning
(ndoc
.location
, "failure", "FAILURE: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
160 toolcontext
.modelbuilder
.failed_entities
+= 1
161 else if res2
!= 0 then
162 var ne
= new HTMLTag("error")
163 ne
.attr
("message", msg
)
165 toolcontext
.warning
(ndoc
.location
, "error", "ERROR: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
166 toolcontext
.modelbuilder
.failed_entities
+= 1
168 toolcontext
.check_errors
174 class SearchAssertVisitor
177 redef fun visit
(node
)
181 else if node
isa AAssertExpr then
190 redef class ModelBuilder
191 var total_entities
= 0
193 var unit_entities
= 0
194 var failed_entities
= 0
196 fun test_markdown
(mmodule
: MModule): HTMLTag
198 var ts
= new HTMLTag("testsuite")
199 toolcontext
.info
("nitunit: doc-unit {mmodule}", 2)
200 if not mmodule2nmodule
.has_key
(mmodule
) then return ts
202 var nmodule
= mmodule2nmodule
[mmodule
]
203 assert nmodule
!= null
205 # usualy, only the original module must be imported in the unit test.
208 if g
!= null and g
.mproject
.name
== "standard" then
209 # except for a unit test in a module of standard
210 # in this case, the whole standard must be imported
211 o
= get_mmodule_by_name
(nmodule
, g
, g
.mproject
.name
).as(not null)
214 ts
.attr
("package", mmodule
.full_name
)
216 var prefix
= toolcontext
.test_dir
217 prefix
= prefix
.join_path
(mmodule
.to_s
)
218 var d2m
= new NitUnitExecutor(toolcontext
, prefix
, o
, ts
)
224 var nmoduledecl
= nmodule
.n_moduledecl
225 if nmoduledecl
== null then break label x
226 var ndoc
= nmoduledecl
.n_doc
227 if ndoc
== null then break label x
229 tc
= new HTMLTag("testcase")
230 # NOTE: jenkins expects a '.' in the classname attr
231 tc
.attr
("classname", "nitunit." + mmodule
.full_name
+ ".<module>")
232 tc
.attr
("name", "<module>")
233 d2m
.extract
(ndoc
, tc
)
235 for nclassdef
in nmodule
.n_classdefs
do
236 var mclassdef
= nclassdef
.mclassdef
237 if mclassdef
== null then continue
238 if nclassdef
isa AStdClassdef then
240 var ndoc
= nclassdef
.n_doc
243 tc
= new HTMLTag("testcase")
244 tc
.attr
("classname", "nitunit." + mmodule
.full_name
+ "." + mclassdef
.mclass
.full_name
)
245 tc
.attr
("name", "<class>")
246 d2m
.extract
(ndoc
, tc
)
249 for npropdef
in nclassdef
.n_propdefs
do
250 var mpropdef
= npropdef
.mpropdef
251 if mpropdef
== null then continue
253 var ndoc
= npropdef
.n_doc
256 tc
= new HTMLTag("testcase")
257 tc
.attr
("classname", "nitunit." + mmodule
.full_name
+ "." + mclassdef
.mclass
.full_name
)
258 tc
.attr
("name", mpropdef
.mproperty
.full_name
)
259 d2m
.extract
(ndoc
, tc
)