nitunit: add more information in UnitTest
[nit.git] / src / testing / testing_base.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 # Base options for testing tools.
16 module testing_base
17
18 import modelize
19 private import parser_util
20 import html
21
22 redef class ToolContext
23 # opt --full
24 var opt_full = new OptionBool("Process also imported modules", "--full")
25 # opt --output
26 var opt_output = new OptionString("Output name (default is 'nitunit.xml')", "-o", "--output")
27 # opt --dirr
28 var opt_dir = new OptionString("Working directory (default is '.nitunit')", "--dir")
29 # opt --no-act
30 var opt_noact = new OptionBool("Does not compile and run tests", "--no-act")
31 # opt --nitc
32 var opt_nitc = new OptionString("nitc compiler to use", "--nitc")
33
34 # Working directory for testing.
35 fun test_dir: String do
36 var dir = opt_dir.value
37 if dir == null then return ".nitunit"
38 return dir
39 end
40
41 # Search the `nitc` compiler to use
42 #
43 # If not `nitc` is suitable, then prints an error and quit.
44 fun find_nitc: String
45 do
46 var nitc = opt_nitc.value
47 if nitc != null then
48 if not nitc.file_exists then
49 fatal_error(null, "error: cannot find `{nitc}` given by --nitc.")
50 abort
51 end
52 return nitc
53 end
54
55 nitc = "NITC".environ
56 if nitc != "" then
57 if not nitc.file_exists then
58 fatal_error(null, "error: cannot find `{nitc}` given by NITC.")
59 abort
60 end
61 return nitc
62 end
63
64 var nit_dir = nit_dir
65 nitc = nit_dir/"bin/nitc"
66 if not nitc.file_exists then
67 fatal_error(null, "Error: cannot find nitc. Set envvar NIT_DIR or NITC or use the --nitc option.")
68 abort
69 end
70 return nitc
71 end
72
73 # Execute a system command in a more safe context than `Sys::system`.
74 fun safe_exec(command: String): Int
75 do
76 info(command, 2)
77 var real_command = """
78 bash -c "
79 ulimit -f {{{ulimit_file}}} 2> /dev/null
80 ulimit -t {{{ulimit_usertime}}} 2> /dev/null
81 {{{command}}}
82 "
83 """
84 return system(real_command)
85 end
86
87 # The maximum size (in KB) of files written by a command executed trough `safe_exec`
88 #
89 # Default: 64MB
90 var ulimit_file = 65536 is writable
91
92 # The maximum amount of cpu time (in seconds) for a command executed trough `safe_exec`
93 #
94 # Default: 10 CPU minute
95 var ulimit_usertime = 600 is writable
96 end
97
98 # A unit test is an elementary test discovered, run and reported by nitunit.
99 #
100 # This class factorizes `DocUnit` and `TestCase`.
101 abstract class UnitTest
102 # The name of the unit to show in messages
103 fun full_name: String is abstract
104
105 # The location of the unit test to show in messages.
106 fun location: Location is abstract
107
108 # Flag that indicates if the unit test was compiled/run.
109 var is_done: Bool = false is writable
110
111 # Error message occurred during test-case execution (or compilation).
112 var error: nullable String = null is writable
113
114 # Was the test case executed at least once?
115 #
116 # This will indicate the status of the test (failture or error)
117 var was_exec = false is writable
118
119 # The raw output of the execution (or compilation)
120 #
121 # It merges the standard output and error output
122 var raw_output: nullable String = null is writable
123
124 # The location where the error occurred, if it makes sense.
125 var error_location: nullable Location = null is writable
126
127 # Return a `<testcase>` XML node in format compatible with Jenkins unit tests.
128 fun to_xml: HTMLTag do
129 var tc = new HTMLTag("testcase")
130 tc.attr("classname", xml_classname)
131 tc.attr("name", xml_name)
132 var error = self.error
133 if error != null then
134 if was_exec then
135 tc.open("error").append("Runtime Error")
136 else
137 tc.open("failure").append("Compilation Error")
138 end
139 tc.open("system-err").append(error.trunc(8192).filter_nonprintable)
140 end
141 return tc
142 end
143
144 # The `classname` attribute of the XML format.
145 #
146 # NOTE: jenkins expects a '.' in the classname attr
147 #
148 # See to_xml
149 fun xml_classname: String is abstract
150
151 # The `name` attribute of the XML format.
152 #
153 # See to_xml
154 fun xml_name: String is abstract
155 end
156
157 redef class String
158 # If needed, truncate `self` at `max_length` characters and append an informative `message`.
159 #
160 # ~~~
161 # assert "hello".trunc(10) == "hello"
162 # assert "hello".trunc(2) == "he[truncated. Full size is 5]"
163 # assert "hello".trunc(2, "...") == "he..."
164 # ~~~
165 fun trunc(max_length: Int, message: nullable String): String
166 do
167 if length <= max_length then return self
168 if message == null then message = "[truncated. Full size is {length}]"
169 return substring(0, max_length) + message
170 end
171
172 # Use a special notation for whitespace characters that are not `'\n'` (LFD) or `' '` (space).
173 #
174 # ~~~
175 # assert "hello".filter_nonprintable == "hello"
176 # assert "\r\n\t".filter_nonprintable == "^13\n^9"
177 # ~~~
178 fun filter_nonprintable: String
179 do
180 var buf = new Buffer
181 for c in self do
182 var cp = c.code_point
183 if cp < 32 and c != '\n' then
184 buf.append "^{cp}"
185 else
186 buf.add c
187 end
188 end
189 return buf.to_s
190 end
191 end