4249df517901fd1d613442f6ba2325c1adc56001
[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 bu nitunit.
99 #
100 # This class factorizes `DocUnit` and `TestCase`.
101 abstract class UnitTest
102
103 # Error occurred during test-case execution.
104 var error: nullable String = null is writable
105
106 # Was the test case executed at least once?
107 var was_exec = false is writable
108
109 # Return the `TestCase` in XML format compatible with Jenkins.
110 #
111 # See to_xml
112 fun to_xml: HTMLTag do
113 var tc = new HTMLTag("testcase")
114 tc.attr("classname", xml_classname)
115 tc.attr("name", xml_name)
116 var error = self.error
117 if error != null then
118 if was_exec then
119 tc.open("error").append("Runtime Error")
120 else
121 tc.open("failure").append("Compilation Error")
122 end
123 tc.open("system-err").append(error.trunc(8192).filter_nonprintable)
124 end
125 return tc
126 end
127
128 # The `classname` attribute of the XML format.
129 #
130 # NOTE: jenkins expects a '.' in the classname attr
131 fun xml_classname: String is abstract
132
133 # The `name` attribute of the XML format.
134 #
135 # See to_xml
136 fun xml_name: String is abstract
137 end
138
139 redef class String
140 # If needed, truncate `self` at `max_length` characters and append an informative `message`.
141 #
142 # ~~~
143 # assert "hello".trunc(10) == "hello"
144 # assert "hello".trunc(2) == "he[truncated. Full size is 5]"
145 # assert "hello".trunc(2, "...") == "he..."
146 # ~~~
147 fun trunc(max_length: Int, message: nullable String): String
148 do
149 if length <= max_length then return self
150 if message == null then message = "[truncated. Full size is {length}]"
151 return substring(0, max_length) + message
152 end
153
154 # Use a special notation for whitespace characters that are not `'\n'` (LFD) or `' '` (space).
155 #
156 # ~~~
157 # assert "hello".filter_nonprintable == "hello"
158 # assert "\r\n\t".filter_nonprintable == "^13\n^9"
159 # ~~~
160 fun filter_nonprintable: String
161 do
162 var buf = new Buffer
163 for c in self do
164 var cp = c.code_point
165 if cp < 32 and c != '\n' then
166 buf.append "^{cp}"
167 else
168 buf.add c
169 end
170 end
171 return buf.to_s
172 end
173 end