rspec-tracer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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