markdown: add a toolcontext attribute
[nit.git] / src / nitunit.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 # Program to extract and execute unit tests from nit source files
16 module nitunit
17
18 import modelize_property
19 intrude import markdown
20 import parser_util
21
22 # Extractor, Executor an Reporter for the tests in a module
23 class NitUnitExecutor
24 super Doc2Mdwn
25
26 # The name of the module to import
27 var modname: String
28
29 # The prefix of the generated Nit source-file
30 var prefix: String
31
32 # The XML node associated to the module
33 var testsuite: HTMLTag
34
35 # Initialize a new e
36 init(toolcontext: ToolContext, prefix: String, modname: String, testsuite: HTMLTag)
37 do
38 super(toolcontext)
39 self.prefix = prefix
40 self.modname = modname
41 self.testsuite = testsuite
42 end
43
44 # All blocks of code from a same `ADoc`
45 var block = new Array[String]
46
47 redef fun process_code(n: HTMLTag, text: String)
48 do
49 # Try to parse it
50 var ast = toolcontext.parse_something(text)
51
52 # We want executable code
53 if not (ast isa AModule or ast isa ABlockExpr or ast isa AExpr) then return
54
55 # Search `assert` in the AST
56 var v = new SearchAssertVisitor
57 v.enter_visit(ast)
58 if not v.foundit then return
59
60 # Add it to the file
61 block.add(text)
62 end
63
64 # used to generate distinct names
65 var cpt = 0
66
67 # The entry point for a new `ndoc` node
68 # Fill the prepated `tc` (testcase) XTM node
69 fun extract(ndoc: ADoc, tc: HTMLTag)
70 do
71 block.clear
72
73 work(ndoc)
74
75 if block.is_empty then return
76
77 cpt += 1
78 var file = "{prefix}{cpt}.nit"
79
80 toolcontext.info("Execute {tc.attrs["classname"]}.{tc.attrs["name"]} in {file}", 2)
81
82 var dir = file.dirname
83 if dir != "" then dir.mkdir
84 var f
85 f = new OFStream.open(file)
86 f.write("# GENERATED FILE\n")
87 f.write("# Example extracted from a documentation\n")
88 var modname = self.modname
89 f.write("import {modname}\n")
90 f.write("\n")
91 for text in block do
92 f.write(text)
93 end
94 f.close
95
96 var cmd = "../bin/nitg --no-color '{file}' -I . >'{file}.out1' 2>&1 </dev/null -o '{file}.bin'"
97 var res = sys.system(cmd)
98 var res2 = 0
99 if res == 0 then
100 res2 = sys.system("./{file}.bin >>'{file}.out1' 2>&1 </dev/null")
101 end
102
103 var msg
104 f = new IFStream.open("{file}.out1")
105 var n2
106 n2 = new HTMLTag("system-err")
107 tc.add n2
108 msg = f.read_all
109 f.close
110
111 n2 = new HTMLTag("system-out")
112 tc.add n2
113 for text in block do n2.append(text)
114
115
116 if res != 0 then
117 var ne = new HTMLTag("failure")
118 ne.attr("message", msg)
119 tc.add ne
120 toolcontext.warning(ndoc.location, "FAILURE: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
121 else if res2 != 0 then
122 var ne = new HTMLTag("error")
123 ne.attr("message", msg)
124 tc.add ne
125 toolcontext.warning(ndoc.location, "ERROR: {tc.attrs["classname"]}.{tc.attrs["name"]} (in {file}): {msg}")
126 end
127 toolcontext.check_errors
128
129 testsuite.add(tc)
130 end
131 end
132
133 class SearchAssertVisitor
134 super Visitor
135 var foundit = false
136 redef fun visit(node)
137 do
138 if foundit then
139 return
140 else if node isa AAssertExpr then
141 foundit = true
142 return
143 else
144 node.visit_all(self)
145 end
146 end
147 end
148
149 redef class ModelBuilder
150 fun test_markdown(mmodule: MModule): HTMLTag
151 do
152 toolcontext.info("nitunit: {mmodule}", 2)
153 var o = mmodule
154 var d = o.public_owner
155 while d != null do
156 o = d
157 d = o.public_owner
158 end
159
160 var ts = new HTMLTag("testsuite")
161 ts.attr("package", mmodule.full_name)
162
163 var prefix = toolcontext.opt_dir.value
164 if prefix == null then prefix = ".nitunit"
165 prefix = prefix.join_path(mmodule.to_s)
166 var d2m = new NitUnitExecutor(toolcontext, prefix, o.name, ts)
167
168 var tc
169
170 if mmodule2nmodule.has_key(mmodule) then
171 var nmodule = mmodule2nmodule[mmodule]
172 do
173 var nmoduledecl = nmodule.n_moduledecl
174 if nmoduledecl == null then break label x
175 var ndoc = nmoduledecl.n_doc
176 if ndoc == null then break label x
177 tc = new HTMLTag("testcase")
178 # NOTE: jenkins expects a '.' in the classname attr
179 tc.attr("classname", mmodule.full_name + ".<module>")
180 tc.attr("name", "<module>")
181 d2m.extract(ndoc, tc)
182 end label x
183 for nclassdef in nmodule.n_classdefs do
184 var mclassdef = nclassdef.mclassdef.as(not null)
185 if nclassdef isa AStdClassdef then
186 var ndoc = nclassdef.n_doc
187 if ndoc != null then
188 tc = new HTMLTag("testcase")
189 tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name)
190 tc.attr("name", "<class>")
191 d2m.extract(ndoc, tc)
192 end
193 end
194 for npropdef in nclassdef.n_propdefs do
195 var mpropdef = npropdef.mpropdef.as(not null)
196 var ndoc = npropdef.n_doc
197 if ndoc != null then
198 tc = new HTMLTag("testcase")
199 tc.attr("classname", mmodule.full_name + "." + mclassdef.mclass.full_name)
200 tc.attr("name", mpropdef.mproperty.full_name)
201 d2m.extract(ndoc, tc)
202 end
203 end
204 end
205 end
206
207 return ts
208 end
209 end
210
211 redef class ToolContext
212 var opt_full = new OptionBool("Process also imported modules", "--full")
213 var opt_output = new OptionString("Output name (default is 'nitunit.xml')", "-o", "--output")
214 var opt_dir = new OptionString("Working directory (default is '.nitunit')", "--dir")
215 end
216
217 var toolcontext = new ToolContext
218
219 toolcontext.option_context.add_option(toolcontext.opt_full, toolcontext.opt_output, toolcontext.opt_dir)
220
221
222 toolcontext.process_options
223 var args = toolcontext.option_context.rest
224 if args.is_empty or toolcontext.opt_help.value then
225 print "usage: nitunit [options] file.nit..."
226 toolcontext.option_context.usage
227 return
228 end
229
230 var model = new Model
231 var modelbuilder = new ModelBuilder(model, toolcontext)
232
233 var mmodules = modelbuilder.parse(args)
234 modelbuilder.run_phases
235
236 var page = new HTMLTag("testsuites")
237
238 if toolcontext.opt_full.value then mmodules = model.mmodules
239
240 for m in mmodules do
241 page.add modelbuilder.test_markdown(m)
242 end
243
244 var file = toolcontext.opt_output.value
245 if file == null then file = "nitunit.xml"
246 page.save(file)