rspec-tracer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +10 -0
  3. data/LICENSE +21 -0
  4. data/README.md +248 -0
  5. data/lib/rspec_tracer/cache.rb +109 -0
  6. data/lib/rspec_tracer/configuration.rb +134 -0
  7. data/lib/rspec_tracer/coverage_reporter.rb +179 -0
  8. data/lib/rspec_tracer/defaults.rb +10 -0
  9. data/lib/rspec_tracer/example.rb +58 -0
  10. data/lib/rspec_tracer/filter.rb +68 -0
  11. data/lib/rspec_tracer/html_reporter/assets/javascripts/application.js +56 -0
  12. data/lib/rspec_tracer/html_reporter/assets/javascripts/libraries/jquery.js +10881 -0
  13. data/lib/rspec_tracer/html_reporter/assets/javascripts/plugins/datatables.js +15381 -0
  14. data/lib/rspec_tracer/html_reporter/assets/stylesheets/application.css +196 -0
  15. data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/datatables.css +459 -0
  16. data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/jquery-ui.css +436 -0
  17. data/lib/rspec_tracer/html_reporter/assets/stylesheets/print.css +92 -0
  18. data/lib/rspec_tracer/html_reporter/assets/stylesheets/reset.css +265 -0
  19. data/lib/rspec_tracer/html_reporter/public/application.css +5 -0
  20. data/lib/rspec_tracer/html_reporter/public/application.js +6 -0
  21. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc.png +0 -0
  22. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc_disabled.png +0 -0
  23. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_both.png +0 -0
  24. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc.png +0 -0
  25. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc_disabled.png +0 -0
  26. data/lib/rspec_tracer/html_reporter/public/favicon.png +0 -0
  27. data/lib/rspec_tracer/html_reporter/public/loading.gif +0 -0
  28. data/lib/rspec_tracer/html_reporter/reporter.rb +180 -0
  29. data/lib/rspec_tracer/html_reporter/views/examples.erb +53 -0
  30. data/lib/rspec_tracer/html_reporter/views/examples_dependency.erb +36 -0
  31. data/lib/rspec_tracer/html_reporter/views/files_dependency.erb +36 -0
  32. data/lib/rspec_tracer/html_reporter/views/layout.erb +32 -0
  33. data/lib/rspec_tracer/reporter.rb +255 -0
  34. data/lib/rspec_tracer/rspec_reporter.rb +43 -0
  35. data/lib/rspec_tracer/rspec_runner.rb +25 -0
  36. data/lib/rspec_tracer/ruby_coverage.rb +9 -0
  37. data/lib/rspec_tracer/runner.rb +299 -0
  38. data/lib/rspec_tracer/source_file.rb +31 -0
  39. data/lib/rspec_tracer/version.rb +5 -0
  40. data/lib/rspec_tracer.rb +243 -0
  41. metadata +122 -0
@@ -0,0 +1,53 @@
1
+ <div class="report_container" id="<%= title_id %>">
2
+ <h2>
3
+ <span class="group_name"><%= title %></span>
4
+ (
5
+ <span class="blue">
6
+ <strong><%= last_run[:actual_count] %></strong>
7
+ </span> examples,
8
+ <% if last_run[:failed_examples].positive? %>
9
+ <span class="red">
10
+ <strong><%= last_run[:failed_examples] %></strong>
11
+ </span> failures,
12
+ <% end %>
13
+ <% if last_run[:pending_examples].positive? %>
14
+ <span class="yellow">
15
+ <strong><%= last_run[:pending_examples] %></strong>
16
+ </span> pending,
17
+ <% end %>
18
+ <span class="blue">
19
+ <strong><%= last_run[:skipped_examples] %></strong>
20
+ </span> skipped by rspec-tracer
21
+ )
22
+ </h2>
23
+
24
+ <a name="<%= title_id %>"></a>
25
+
26
+ <div class="report-table--responsive">
27
+ <table class="report-table">
28
+ <thead>
29
+ <tr>
30
+ <th>ID</th>
31
+ <th>Description</th>
32
+ <th>Location</th>
33
+ <th>Run Reason</th>
34
+ <th>Result</th>
35
+ <th>Run At</th>
36
+ </tr>
37
+ </thead>
38
+
39
+ <tbody>
40
+ <% examples.each do |example| %>
41
+ <tr>
42
+ <td><%= example[:id] %></td>
43
+ <td><%= example[:description] %></td>
44
+ <td><%= example[:location] %></td>
45
+ <td><strong class="<%= example_status_css_class(example[:status]) %>"><%= example[:status] %></strong></td>
46
+ <td><strong class="<%= example_result_css_class(example[:result]) %>"><%= example[:result] %></strong></td>
47
+ <td width="8%"><%= example[:last_run] %></td>
48
+ </tr>
49
+ <% end %>
50
+ </tbody>
51
+ </table>
52
+ </div>
53
+ </div>
@@ -0,0 +1,36 @@
1
+ <div class="report_container" id="<%= title_id %>">
2
+ <h2>
3
+ <span class="group_name"><%= title %></span>
4
+ (
5
+ <span class="blue">
6
+ <strong><%= examples_dependency.count %></strong>
7
+ </span> examples
8
+ )
9
+ </h2>
10
+
11
+ <a name="<%= title_id %>"></a>
12
+
13
+ <div class="report-table--responsive">
14
+ <table class="report-table">
15
+ <thead>
16
+ <tr>
17
+ <th>Example ID</th>
18
+ <th>Example</th>
19
+ <th>Files Count</th>
20
+ <th data-orderable="false">Files Name</th>
21
+ </tr>
22
+ </thead>
23
+
24
+ <tbody>
25
+ <% examples_dependency.each do |example_dependency| %>
26
+ <tr>
27
+ <td><%= example_dependency[:example_id] %></td>
28
+ <td><%= example_dependency[:example] %></td>
29
+ <td class="number" width="8%"><strong><%= example_dependency[:files_count] %></strong></td>
30
+ <td><%= example_dependency[:files] %></td>
31
+ </tr>
32
+ <% end %>
33
+ </tbody>
34
+ </table>
35
+ </div>
36
+ </div>
@@ -0,0 +1,36 @@
1
+ <div class="report_container" id="<%= title_id %>">
2
+ <h2>
3
+ <span class="group_name"><%= title %></span>
4
+ (
5
+ <span class="blue">
6
+ <strong><%= files_dependency.count %></strong>
7
+ </span> files
8
+ )
9
+ </h2>
10
+
11
+ <a name="<%= title_id %>"></a>
12
+
13
+ <div class="report-table--responsive">
14
+ <table class="report-table">
15
+ <thead>
16
+ <tr>
17
+ <th searchable="true">File</th>
18
+ <th>Examples Count</th>
19
+ <th>Files Count</th>
20
+ <th data-orderable="false">Dependent Files</th>
21
+ </tr>
22
+ </thead>
23
+
24
+ <tbody>
25
+ <% files_dependency.each do |file_dependency| %>
26
+ <tr class="t-example">
27
+ <td><%= file_dependency[:name] %></td>
28
+ <td class="number" width="8%"><strong><%= file_dependency[:example_count] %></strong></td>
29
+ <td class="number" width="8%"><strong><%= file_dependency[:file_count] %></strong></td>
30
+ <td><%= file_dependency[:files] %></td>
31
+ </tr>
32
+ <% end %>
33
+ </tbody>
34
+ </table>
35
+ </div>
36
+ </div>
@@ -0,0 +1,32 @@
1
+ <!DOCTYPE html>
2
+
3
+ <html xmlns='http://www.w3.org/1999/xhtml'>
4
+ <head>
5
+ <title>RSpec Tracer Report for <%= RSpecTracer.project_name %></title>
6
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
7
+ <script src='<%= assets_path('application.js') %>' type='text/javascript'></script>
8
+ <link href='<%= assets_path('application.css') %>' media='screen, projection, print' rel='stylesheet' type='text/css' />
9
+ <link rel="icon" type="image/png" href="<%= assets_path('favicon.png') %>" />
10
+ </head>
11
+
12
+ <body>
13
+ <div id="loading">
14
+ <img src="<%= assets_path('loading.gif') %>" width="60" height="60" alt="loading" />
15
+ </div>
16
+
17
+ <div id="wrapper" class="hide">
18
+ <ul class="group_tabs"></ul>
19
+
20
+ <div id="content">
21
+ <%= formatted_examples('Examples', examples.values) %>
22
+ <%= formatted_examples_dependency('Examples Dependency', examples_dependency) %>
23
+ <%= formatted_files_dependency("Files Dependency", files_dependency) %>
24
+ </div>
25
+
26
+ <div id="footer">
27
+ Generated by <a href="https://github.com/avmnu-sng/rspec-tracer">rspec-tracer</a>
28
+ v<%= RSpecTracer::VERSION %>
29
+ </div>
30
+ </div>
31
+ </body>
32
+ </html>
@@ -0,0 +1,255 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecTracer
4
+ class Reporter
5
+ attr_reader :all_examples, :pending_examples, :all_files, :dependency,
6
+ :reverse_dependency, :examples_coverage, :last_run
7
+
8
+ def initialize
9
+ initialize_examples
10
+ initialize_files
11
+ initialize_dependency
12
+ initialize_coverage
13
+ end
14
+
15
+ def register_example(example)
16
+ @all_examples[example[:example_id]] = example
17
+ end
18
+
19
+ def on_example_skipped(example_id)
20
+ @skipped_examples << example_id
21
+ end
22
+
23
+ def on_example_passed(example_id, result)
24
+ @all_examples[example_id][:execution_result] = formatted_execution_result(result)
25
+ end
26
+
27
+ def on_example_failed(example_id, result)
28
+ @failed_examples << example_id
29
+ @all_examples[example_id][:execution_result] = formatted_execution_result(result)
30
+ end
31
+
32
+ def on_example_pending(example_id, result)
33
+ @pending_examples << example_id
34
+ @all_examples[example_id][:execution_result] = formatted_execution_result(result)
35
+ end
36
+
37
+ def register_deleted_examples(seen_examples)
38
+ @deleted_examples = seen_examples.keys.to_set - (@skipped_examples | @all_examples.keys)
39
+
40
+ @deleted_examples.select! do |example_id|
41
+ example = seen_examples[example_id]
42
+
43
+ file_changed?(example[:file_name]) || file_changed?(example[:rerun_file_name])
44
+ end
45
+ end
46
+
47
+ def register_failed_example(example_id)
48
+ @failed_examples << example_id
49
+ end
50
+
51
+ def register_pending_example(example_id)
52
+ @pending_examples << example_id
53
+ end
54
+
55
+ def example_skipped?(example_id)
56
+ @skipped_examples.include?(example_id)
57
+ end
58
+
59
+ def example_failed?(example_id)
60
+ @failed_examples.include?(example_id)
61
+ end
62
+
63
+ def example_pending?(example_id)
64
+ @pending_examples.include?(example_id)
65
+ end
66
+
67
+ def example_deleted?(example_id)
68
+ @deleted_examples.include?(example_id)
69
+ end
70
+
71
+ def register_source_file(source_file)
72
+ @all_files[source_file[:file_name]] = source_file
73
+ end
74
+
75
+ def on_file_deleted(file_name)
76
+ @deleted_files << file_name
77
+ end
78
+
79
+ def on_file_modified(file_name)
80
+ @modified_files << file_name
81
+ end
82
+
83
+ def file_deleted?(file_name)
84
+ @deleted_files.include?(file_name)
85
+ end
86
+
87
+ def file_modified?(file_name)
88
+ @modified_files.include?(file_name)
89
+ end
90
+
91
+ def file_changed?(file_name)
92
+ file_deleted?(file_name) || file_modified?(file_name)
93
+ end
94
+
95
+ def register_dependency(example_id, file_name)
96
+ @dependency[example_id] << file_name
97
+ end
98
+
99
+ def register_examples_coverage(examples_coverage)
100
+ @examples_coverage = examples_coverage
101
+ end
102
+
103
+ def generate_reverse_dependency_report
104
+ @dependency.each_pair do |example_id, files|
105
+ example_file = @all_examples[example_id][:rerun_file_name]
106
+
107
+ files.each do |file_name|
108
+ @reverse_dependency[file_name][:example_count] += 1
109
+ @reverse_dependency[file_name][:examples][example_file] += 1
110
+ end
111
+ end
112
+
113
+ format_reverse_dependency_report
114
+ end
115
+
116
+ def generate_last_run_report
117
+ @last_run = {
118
+ run_id: @run_id,
119
+ pid: RSpecTracer.pid,
120
+ actual_count: RSpec.world.example_count + @skipped_examples.count,
121
+ example_count: RSpec.world.example_count,
122
+ failed_examples: @failed_examples.count,
123
+ skipped_examples: @skipped_examples.count,
124
+ pending_examples: @pending_examples.count
125
+ }
126
+ end
127
+
128
+ def write_reports
129
+ @run_id = Digest::MD5.hexdigest(@all_examples.keys.sort.to_json)
130
+ @cache_dir = File.join(RSpecTracer.cache_path, @run_id)
131
+
132
+ FileUtils.mkdir_p(@cache_dir)
133
+
134
+ %i[
135
+ all_examples
136
+ failed_examples
137
+ pending_examples
138
+ all_files
139
+ dependency
140
+ reverse_dependency
141
+ examples_coverage
142
+ last_run
143
+ ].each { |report_type| send("write_#{report_type}_report") }
144
+
145
+ puts "RSpec tracer reports generated to #{@cache_dir}"
146
+ end
147
+
148
+ private
149
+
150
+ def initialize_examples
151
+ @all_examples = {}
152
+ @failed_examples = Set.new
153
+ @skipped_examples = Set.new
154
+ @pending_examples = Set.new
155
+ @deleted_examples = Set.new
156
+ end
157
+
158
+ def initialize_files
159
+ @all_files = {}
160
+ @modified_files = Set.new
161
+ @deleted_files = Set.new
162
+ end
163
+
164
+ def initialize_dependency
165
+ @dependency = Hash.new { |hash, key| hash[key] = Set.new }
166
+ @reverse_dependency = Hash.new do |examples, file_name|
167
+ examples[file_name] = {
168
+ example_count: 0,
169
+ examples: Hash.new(0)
170
+ }
171
+ end
172
+ end
173
+
174
+ def initialize_coverage
175
+ @examples_coverage = Hash.new do |examples, example_id|
176
+ examples[example_id] = Hash.new do |files, file_name|
177
+ files[file_name] = {}
178
+ end
179
+ end
180
+ end
181
+
182
+ def formatted_execution_result(result)
183
+ {
184
+ started_at: result.started_at.utc,
185
+ finished_at: result.finished_at.utc,
186
+ run_time: result.run_time,
187
+ status: result.status.to_s
188
+ }
189
+ end
190
+
191
+ def format_reverse_dependency_report
192
+ @reverse_dependency.transform_values! do |data|
193
+ {
194
+ example_count: data[:example_count],
195
+ examples: data[:examples].sort_by { |file_name, count| [-count, file_name] }.to_h
196
+ }
197
+ end
198
+
199
+ report = @reverse_dependency.sort_by do |file_name, data|
200
+ [-data[:example_count], file_name]
201
+ end
202
+
203
+ @reverse_dependency = report.to_h
204
+ end
205
+
206
+ def write_all_examples_report
207
+ file_name = File.join(@cache_dir, 'all_examples.json')
208
+
209
+ File.write(file_name, JSON.pretty_generate(@all_examples))
210
+ end
211
+
212
+ def write_failed_examples_report
213
+ file_name = File.join(@cache_dir, 'failed_examples.json')
214
+
215
+ File.write(file_name, JSON.pretty_generate(@failed_examples.to_a))
216
+ end
217
+
218
+ def write_pending_examples_report
219
+ file_name = File.join(@cache_dir, 'pending_examples.json')
220
+
221
+ File.write(file_name, JSON.pretty_generate(@pending_examples.to_a))
222
+ end
223
+
224
+ def write_all_files_report
225
+ file_name = File.join(@cache_dir, 'all_files.json')
226
+
227
+ File.write(file_name, JSON.pretty_generate(@all_files))
228
+ end
229
+
230
+ def write_dependency_report
231
+ file_name = File.join(@cache_dir, 'dependency.json')
232
+
233
+ File.write(file_name, JSON.pretty_generate(@dependency))
234
+ end
235
+
236
+ def write_reverse_dependency_report
237
+ file_name = File.join(@cache_dir, 'reverse_dependency.json')
238
+
239
+ File.write(file_name, JSON.pretty_generate(@reverse_dependency))
240
+ end
241
+
242
+ def write_examples_coverage_report
243
+ file_name = File.join(@cache_dir, 'examples_coverage.json')
244
+
245
+ File.write(file_name, JSON.pretty_generate(@examples_coverage))
246
+ end
247
+
248
+ def write_last_run_report
249
+ file_name = File.join(RSpecTracer.cache_path, 'last_run.json')
250
+ last_run_data = @last_run.merge(run_id: @run_id, timestamp: Time.now.utc)
251
+
252
+ File.write(file_name, JSON.pretty_generate(last_run_data))
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecTracer
4
+ module RSpecReporter
5
+ def example_started(example)
6
+ RSpecTracer.coverage_reporter.record_coverage
7
+ RSpecTracer.start_example_trace if RSpecTracer.trace_example?
8
+
9
+ super(example)
10
+ end
11
+
12
+ def example_finished(example)
13
+ passed = example.execution_result.status == :passed
14
+ RSpecTracer.stop_example_trace(passed) if RSpecTracer.trace_example?
15
+
16
+ example_id = example.metadata[:rspec_tracer_example_id]
17
+ RSpecTracer.coverage_reporter.compute_diff(example_id)
18
+
19
+ super(example)
20
+ end
21
+
22
+ def example_passed(example)
23
+ example_id = example.metadata[:rspec_tracer_example_id]
24
+ RSpecTracer.runner.on_example_passed(example_id, example.execution_result)
25
+
26
+ super(example)
27
+ end
28
+
29
+ def example_failed(example)
30
+ example_id = example.metadata[:rspec_tracer_example_id]
31
+ RSpecTracer.runner.on_example_failed(example_id, example.execution_result)
32
+
33
+ super(example)
34
+ end
35
+
36
+ def example_pending(example)
37
+ example_id = example.metadata[:rspec_tracer_example_id]
38
+ RSpecTracer.runner.on_example_pending(example_id, example.execution_result)
39
+
40
+ super(example)
41
+ end
42
+ end
43
+ end