actir 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,417 @@
1
+ require 'erb'
2
+
3
+ module Actir
4
+ module ParallelTests
5
+ class HtmlFormatter
6
+
7
+ include ERB::Util # For the #h method.
8
+ def initialize(output)
9
+ @output = output
10
+ end
11
+
12
+ def print_html_start
13
+ @output.puts HTML_HEADER
14
+ @output.puts REPORT_HEADER
15
+ end
16
+
17
+ def print_example_group_end
18
+ @output.puts " </dl>"
19
+ @output.puts "</div>"
20
+ end
21
+
22
+ def print_example_group_start(group_id, description, number_of_parents)
23
+ @output.puts "<div id=\"div_group_#{group_id}\" class=\"example_group passed\">"
24
+ @output.puts " <dl #{indentation_style(number_of_parents)}>"
25
+ @output.puts " <dt id=\"example_group_#{group_id}\" class=\"passed\">#{h(description)}</dt>"
26
+ end
27
+
28
+ def print_example_passed(description, run_time)
29
+ formatted_run_time = "%.5f" % run_time
30
+ @output.puts " <dd class=\"example passed\">" \
31
+ "<span class=\"passed_spec_name\">#{h(description)}</span>" \
32
+ "<span class='duration'>#{formatted_run_time}s</span></dd>"
33
+ end
34
+
35
+ # rubocop:disable Style/ParameterLists
36
+ def print_example_failed(pending_fixed, description, run_time, failure_id,
37
+ exception, extra_content, escape_backtrace=false)
38
+ # rubocop:enable Style/ParameterLists
39
+ formatted_run_time = "%.5f" % run_time
40
+
41
+ @output.puts " <dd class=\"example #{pending_fixed ? 'pending_fixed' : 'failed'}\">"
42
+ @output.puts " <span class=\"failed_spec_name\">#{h(description)}</span>"
43
+ @output.puts " <span class=\"duration\">#{formatted_run_time}s</span>"
44
+ @output.puts " <div class=\"failure\" id=\"failure_#{failure_id}\">"
45
+ if exception
46
+ @output.puts " <div class=\"message\"><pre>#{h(exception[:message])}</pre></div>"
47
+ if escape_backtrace
48
+ @output.puts " <div class=\"backtrace\"><pre>#{h exception[:backtrace]}</pre></div>"
49
+ else
50
+ @output.puts " <div class=\"backtrace\"><pre>#{exception[:backtrace]}</pre></div>"
51
+ end
52
+ end
53
+ @output.puts extra_content if extra_content
54
+ @output.puts " </div>"
55
+ @output.puts " </dd>"
56
+ end
57
+
58
+ def print_example_pending(description, pending_message)
59
+ @output.puts " <dd class=\"example not_implemented\">" \
60
+ "<span class=\"not_implemented_spec_name\">#{h(description)} " \
61
+ "(PENDING: #{h(pending_message)})</span></dd>"
62
+ end
63
+
64
+ def print_summary(duration, example_count, failure_count, pending_count)
65
+ totals = "#{example_count} example#{'s' unless example_count == 1}, "
66
+ totals << "#{failure_count} failure#{'s' unless failure_count == 1}"
67
+ totals << ", #{pending_count} pending" if pending_count > 0
68
+
69
+ formatted_duration = "%.5f" % duration
70
+
71
+ @output.puts "<script type=\"text/javascript\">" \
72
+ "document.getElementById('duration').innerHTML = \"Finished in " \
73
+ "<strong>#{formatted_duration} seconds</strong>\";</script>"
74
+ @output.puts "<script type=\"text/javascript\">" \
75
+ "document.getElementById('totals').innerHTML = \"#{totals}\";</script>"
76
+ @output.puts "</div>"
77
+ @output.puts "</div>"
78
+ @output.puts "</body>"
79
+ @output.puts "</html>"
80
+ end
81
+
82
+ def flush
83
+ @output.flush
84
+ end
85
+
86
+ def move_progress(percent_done)
87
+ @output.puts " <script type=\"text/javascript\">moveProgressBar('#{percent_done}');</script>"
88
+ @output.flush
89
+ end
90
+
91
+ def make_header_red
92
+ @output.puts " <script type=\"text/javascript\">makeRed('test-header');</script>"
93
+ end
94
+
95
+ def make_header_yellow
96
+ @output.puts " <script type=\"text/javascript\">makeYellow('test-header');</script>"
97
+ end
98
+
99
+ def make_example_group_header_red(group_id)
100
+ @output.puts " <script type=\"text/javascript\">" \
101
+ "makeRed('div_group_#{group_id}');</script>"
102
+ @output.puts " <script type=\"text/javascript\">" \
103
+ "makeRed('example_group_#{group_id}');</script>"
104
+ end
105
+
106
+ def make_example_group_header_yellow(group_id)
107
+ @output.puts " <script type=\"text/javascript\">" \
108
+ "makeYellow('div_group_#{group_id}');</script>"
109
+ @output.puts " <script type=\"text/javascript\">" \
110
+ "makeYellow('example_group_#{group_id}');</script>"
111
+ end
112
+
113
+ private
114
+
115
+ def indentation_style(number_of_parents)
116
+ "style=\"margin-left: #{(number_of_parents - 1) * 15}px;\""
117
+ end
118
+
119
+ # rubocop:disable LineLength
120
+ REPORT_HEADER = <<-EOF
121
+ <div class="test-report">
122
+
123
+ <div id="test-header">
124
+ <div id="label">
125
+ <h1>test Code Examples</h1>
126
+ </div>
127
+
128
+ <div id="display-filters">
129
+ <input id="passed_checkbox" name="passed_checkbox" type="checkbox" checked="checked" onchange="apply_filters()" value="1" /> <label for="passed_checkbox">Passed</label>
130
+ <input id="failed_checkbox" name="failed_checkbox" type="checkbox" checked="checked" onchange="apply_filters()" value="2" /> <label for="failed_checkbox">Failed</label>
131
+ <input id="pending_checkbox" name="pending_checkbox" type="checkbox" checked="checked" onchange="apply_filters()" value="3" /> <label for="pending_checkbox">Pending</label>
132
+ </div>
133
+
134
+ <div id="summary">
135
+ <p id="totals">&#160;</p>
136
+ <p id="duration">&#160;</p>
137
+ </div>
138
+ </div>
139
+
140
+
141
+ <div class="results">
142
+ EOF
143
+ # rubocop:enable LineLength
144
+
145
+ # rubocop:disable LineLength
146
+ GLOBAL_SCRIPTS = <<-EOF
147
+
148
+ function addClass(element_id, classname) {
149
+ document.getElementById(element_id).className += (" " + classname);
150
+ }
151
+
152
+ function removeClass(element_id, classname) {
153
+ var elem = document.getElementById(element_id);
154
+ var classlist = elem.className.replace(classname,'');
155
+ elem.className = classlist;
156
+ }
157
+
158
+ function moveProgressBar(percentDone) {
159
+ document.getElementById("test-header").style.width = percentDone +"%";
160
+ }
161
+
162
+ function makeRed(element_id) {
163
+ removeClass(element_id, 'passed');
164
+ removeClass(element_id, 'not_implemented');
165
+ addClass(element_id,'failed');
166
+ }
167
+
168
+ function makeYellow(element_id) {
169
+ var elem = document.getElementById(element_id);
170
+ if (elem.className.indexOf("failed") == -1) { // class doesn't includes failed
171
+ if (elem.className.indexOf("not_implemented") == -1) { // class doesn't include not_implemented
172
+ removeClass(element_id, 'passed');
173
+ addClass(element_id,'not_implemented');
174
+ }
175
+ }
176
+ }
177
+
178
+ function apply_filters() {
179
+ var passed_filter = document.getElementById('passed_checkbox').checked;
180
+ var failed_filter = document.getElementById('failed_checkbox').checked;
181
+ var pending_filter = document.getElementById('pending_checkbox').checked;
182
+
183
+ assign_display_style("example passed", passed_filter);
184
+ assign_display_style("example failed", failed_filter);
185
+ assign_display_style("example not_implemented", pending_filter);
186
+
187
+ assign_display_style_for_group("example_group passed", passed_filter);
188
+ assign_display_style_for_group("example_group not_implemented", pending_filter, pending_filter || passed_filter);
189
+ assign_display_style_for_group("example_group failed", failed_filter, failed_filter || pending_filter || passed_filter);
190
+ }
191
+
192
+ function get_display_style(display_flag) {
193
+ var style_mode = 'none';
194
+ if (display_flag == true) {
195
+ style_mode = 'block';
196
+ }
197
+ return style_mode;
198
+ }
199
+
200
+ function assign_display_style(classname, display_flag) {
201
+ var style_mode = get_display_style(display_flag);
202
+ var elems = document.getElementsByClassName(classname)
203
+ for (var i=0; i<elems.length;i++) {
204
+ elems[i].style.display = style_mode;
205
+ }
206
+ }
207
+
208
+ function assign_display_style_for_group(classname, display_flag, subgroup_flag) {
209
+ var display_style_mode = get_display_style(display_flag);
210
+ var subgroup_style_mode = get_display_style(subgroup_flag);
211
+ var elems = document.getElementsByClassName(classname)
212
+ for (var i=0; i<elems.length;i++) {
213
+ var style_mode = display_style_mode;
214
+ if ((display_flag != subgroup_flag) && (elems[i].getElementsByTagName('dt')[0].innerHTML.indexOf(", ") != -1)) {
215
+ elems[i].style.display = subgroup_style_mode;
216
+ } else {
217
+ elems[i].style.display = display_style_mode;
218
+ }
219
+ }
220
+ }
221
+ EOF
222
+ # rubocop:enable LineLength
223
+
224
+ GLOBAL_STYLES = <<-EOF
225
+ #test-header {
226
+ background: #65C400; color: #fff; height: 4em;
227
+ }
228
+
229
+ .test-report h1 {
230
+ margin: 0px 10px 0px 10px;
231
+ padding: 10px;
232
+ font-family: "Lucida Grande", Helvetica, sans-serif;
233
+ font-size: 1.8em;
234
+ position: absolute;
235
+ }
236
+
237
+ #label {
238
+ float:left;
239
+ }
240
+
241
+ #display-filters {
242
+ float:left;
243
+ padding: 28px 0 0 40%;
244
+ font-family: "Lucida Grande", Helvetica, sans-serif;
245
+ }
246
+
247
+ #summary {
248
+ float:right;
249
+ padding: 5px 10px;
250
+ font-family: "Lucida Grande", Helvetica, sans-serif;
251
+ text-align: right;
252
+ }
253
+
254
+ #summary p {
255
+ margin: 0 0 0 2px;
256
+ }
257
+
258
+ #summary #totals {
259
+ font-size: 1.2em;
260
+ }
261
+
262
+ .example_group {
263
+ margin: 0 10px 5px;
264
+ background: #fff;
265
+ }
266
+
267
+ dl {
268
+ margin: 0; padding: 0 0 5px;
269
+ font: normal 11px "Lucida Grande", Helvetica, sans-serif;
270
+ }
271
+
272
+ dt {
273
+ padding: 3px;
274
+ background: #65C400;
275
+ color: #fff;
276
+ font-weight: bold;
277
+ }
278
+
279
+ dd {
280
+ margin: 5px 0 5px 5px;
281
+ padding: 3px 3px 3px 18px;
282
+ }
283
+
284
+ dd .duration {
285
+ padding-left: 5px;
286
+ text-align: right;
287
+ right: 0px;
288
+ float:right;
289
+ }
290
+
291
+ dd.example.passed {
292
+ border-left: 5px solid #65C400;
293
+ border-bottom: 1px solid #65C400;
294
+ background: #DBFFB4; color: #3D7700;
295
+ }
296
+
297
+ dd.example.not_implemented {
298
+ border-left: 5px solid #FAF834;
299
+ border-bottom: 1px solid #FAF834;
300
+ background: #FCFB98; color: #131313;
301
+ }
302
+
303
+ dd.example.pending_fixed {
304
+ border-left: 5px solid #0000C2;
305
+ border-bottom: 1px solid #0000C2;
306
+ color: #0000C2; background: #D3FBFF;
307
+ }
308
+
309
+ dd.example.failed {
310
+ border-left: 5px solid #C20000;
311
+ border-bottom: 1px solid #C20000;
312
+ color: #C20000; background: #FFFBD3;
313
+ }
314
+
315
+
316
+ dt.not_implemented {
317
+ color: #000000; background: #FAF834;
318
+ }
319
+
320
+ dt.pending_fixed {
321
+ color: #FFFFFF; background: #C40D0D;
322
+ }
323
+
324
+ dt.failed {
325
+ color: #FFFFFF; background: #C40D0D;
326
+ }
327
+
328
+
329
+ #test-header.not_implemented {
330
+ color: #000000; background: #FAF834;
331
+ }
332
+
333
+ #test-header.pending_fixed {
334
+ color: #FFFFFF; background: #C40D0D;
335
+ }
336
+
337
+ #test-header.failed {
338
+ color: #FFFFFF; background: #C40D0D;
339
+ }
340
+
341
+
342
+ .backtrace {
343
+ color: #000;
344
+ font-size: 12px;
345
+ }
346
+
347
+ a {
348
+ color: #BE5C00;
349
+ }
350
+
351
+ /* Ruby code, style similar to vibrant ink */
352
+ .ruby {
353
+ font-size: 12px;
354
+ font-family: monospace;
355
+ color: white;
356
+ background-color: black;
357
+ padding: 0.1em 0 0.2em 0;
358
+ }
359
+
360
+ .ruby .keyword { color: #FF6600; }
361
+ .ruby .constant { color: #339999; }
362
+ .ruby .attribute { color: white; }
363
+ .ruby .global { color: white; }
364
+ .ruby .module { color: white; }
365
+ .ruby .class { color: white; }
366
+ .ruby .string { color: #66FF00; }
367
+ .ruby .ident { color: white; }
368
+ .ruby .method { color: #FFCC00; }
369
+ .ruby .number { color: white; }
370
+ .ruby .char { color: white; }
371
+ .ruby .comment { color: #9933CC; }
372
+ .ruby .symbol { color: white; }
373
+ .ruby .regex { color: #44B4CC; }
374
+ .ruby .punct { color: white; }
375
+ .ruby .escape { color: white; }
376
+ .ruby .interp { color: white; }
377
+ .ruby .expr { color: white; }
378
+
379
+ .ruby .offending { background-color: gray; }
380
+ .ruby .linenum {
381
+ width: 75px;
382
+ padding: 0.1em 1em 0.2em 0;
383
+ color: #000000;
384
+ background-color: #FFFBD3;
385
+ }
386
+ EOF
387
+
388
+ HTML_HEADER = <<-EOF
389
+ <!DOCTYPE html>
390
+ <html lang='en'>
391
+ <head>
392
+ <title>Test results</title>
393
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
394
+ <meta http-equiv="Expires" content="-1" />
395
+ <meta http-equiv="Pragma" content="no-cache" />
396
+ <style type="text/css">
397
+ body {
398
+ margin: 0;
399
+ padding: 0;
400
+ background: #fff;
401
+ font-size: 80%;
402
+ }
403
+ </style>
404
+ <script type="text/javascript">
405
+ // <![CDATA[
406
+ #{GLOBAL_SCRIPTS}
407
+ // ]]>
408
+ </script>
409
+ <style type="text/css">
410
+ #{GLOBAL_STYLES}
411
+ </style>
412
+ </head>
413
+ <body>
414
+ EOF
415
+ end
416
+ end
417
+ end
@@ -0,0 +1,145 @@
1
+ module Actir
2
+ module ParallelTests
3
+ class HtmlReport
4
+
5
+ def initialize(output)
6
+ super(output)
7
+ @failed_examples = []
8
+ @example_group_number = 0
9
+ @example_number = 0
10
+ @header_red = nil
11
+ @printer = HtmlPrinter.new(output)
12
+ end
13
+
14
+ def start(notification)
15
+ super
16
+ @printer.print_html_start
17
+ @printer.flush
18
+ end
19
+
20
+ def example_group_started(notification)
21
+ super
22
+ @example_group_red = false
23
+ @example_group_number += 1
24
+
25
+ @printer.print_example_group_end unless example_group_number == 1
26
+ @printer.print_example_group_start(example_group_number,
27
+ notification.group.description,
28
+ notification.group.parent_groups.size)
29
+ @printer.flush
30
+ end
31
+
32
+ def start_dump(_notification)
33
+ @printer.print_example_group_end
34
+ @printer.flush
35
+ end
36
+
37
+ def example_started(_notification)
38
+ @example_number += 1
39
+ end
40
+
41
+ def example_passed(passed)
42
+ @printer.move_progress(percent_done)
43
+ @printer.print_example_passed(passed.example.description, passed.example.execution_result.run_time)
44
+ @printer.flush
45
+ end
46
+
47
+ def example_failed(failure)
48
+ @failed_examples << failure.example
49
+ unless @header_red
50
+ @header_red = true
51
+ @printer.make_header_red
52
+ end
53
+
54
+ unless @example_group_red
55
+ @example_group_red = true
56
+ @printer.make_example_group_header_red(example_group_number)
57
+ end
58
+
59
+ @printer.move_progress(percent_done)
60
+
61
+ example = failure.example
62
+
63
+ exception = failure.exception
64
+ exception_details = if exception
65
+ {
66
+ :message => exception.message,
67
+ :backtrace => failure.formatted_backtrace.join("\n")
68
+ }
69
+ else
70
+ false
71
+ end
72
+ extra = extra_failure_content(failure)
73
+
74
+ @printer.print_example_failed(
75
+ example.execution_result.pending_fixed,
76
+ example.description,
77
+ example.execution_result.run_time,
78
+ @failed_examples.size,
79
+ exception_details,
80
+ (extra == "") ? false : extra,
81
+ true
82
+ )
83
+ @printer.flush
84
+ end
85
+
86
+ def example_pending(pending)
87
+ example = pending.example
88
+
89
+ @printer.make_header_yellow unless @header_red
90
+ @printer.make_example_group_header_yellow(example_group_number) unless @example_group_red
91
+ @printer.move_progress(percent_done)
92
+ @printer.print_example_pending(example.description, example.execution_result.pending_message)
93
+ @printer.flush
94
+ end
95
+
96
+ def dump_summary(summary)
97
+ @printer.print_summary(
98
+ summary.duration,
99
+ summary.example_count,
100
+ summary.failure_count,
101
+ summary.pending_count
102
+ )
103
+ @printer.flush
104
+ end
105
+
106
+ private
107
+
108
+ # If these methods are declared with attr_reader Ruby will issue a
109
+ # warning because they are private.
110
+ # rubocop:disable Style/TrivialAccessors
111
+
112
+ # The number of the currently running example_group.
113
+ def example_group_number
114
+ @example_group_number
115
+ end
116
+
117
+ # The number of the currently running example (a global counter).
118
+ def example_number
119
+ @example_number
120
+ end
121
+ # rubocop:enable Style/TrivialAccessors
122
+
123
+ def percent_done
124
+ result = 100.0
125
+ if @example_count > 0
126
+ result = (((example_number).to_f / @example_count.to_f * 1000).to_i / 10.0).to_f
127
+ end
128
+ result
129
+ end
130
+
131
+ # Override this method if you wish to output extra HTML for a failed
132
+ # spec. For example, you could output links to images or other files
133
+ # produced during the specs.
134
+ def extra_failure_content(failure)
135
+ RSpec::Support.require_rspec_core "formatters/snippet_extractor"
136
+ backtrace = failure.exception.backtrace.map do |line|
137
+ RSpec.configuration.backtrace_formatter.backtrace_line(line)
138
+ end
139
+ backtrace.compact!
140
+ @snippet_extractor ||= SnippetExtractor.new
141
+ " <pre class=\"ruby\"><code>#{@snippet_extractor.snippet(backtrace)}</code></pre>"
142
+ end
143
+ end
144
+ end
145
+ end