rspec-tracer 0.4.0 → 0.6.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +36 -0
- data/README.md +67 -15
- data/lib/rspec_tracer/cache.rb +27 -3
- data/lib/rspec_tracer/configuration.rb +6 -0
- data/lib/rspec_tracer/coverage_reporter.rb +7 -3
- data/lib/rspec_tracer/defaults.rb +7 -1
- data/lib/rspec_tracer/html_reporter/reporter.rb +28 -6
- data/lib/rspec_tracer/html_reporter/views/flaky_examples.erb +38 -0
- data/lib/rspec_tracer/html_reporter/views/layout.erb +3 -0
- data/lib/rspec_tracer/remote_cache/cache.rb +81 -33
- data/lib/rspec_tracer/reporter.rb +32 -3
- data/lib/rspec_tracer/rspec_runner.rb +6 -1
- data/lib/rspec_tracer/runner.rb +99 -39
- data/lib/rspec_tracer/time_formatter.rb +55 -0
- data/lib/rspec_tracer/version.rb +1 -1
- data/lib/rspec_tracer.rb +28 -6
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a86a9027964446fd10fc51379221d718e15699f6f93ba0c56af654273ccf632d
|
4
|
+
data.tar.gz: 1ade4a1b9a14b8cf0a10467889e0631d4b4e30871118734a6d2b4705138b19b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c251e47b35e667c0b847c54bb7a7c96ad33c39b8c91f48bcaf96c06cb9b017155ed760398f24e382ee93c94fe8bf7b40289fd9c87518bbd467d42b2bae91daac
|
7
|
+
data.tar.gz: a314d23f2b7998cb64b5183a5a0da4d1d100039accfd28998ccdf15d451f633fb4181688951de400bbaf82095253f56d73e585632e411657ada157d0e285a1b0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,39 @@
|
|
1
|
+
## [0.6.2] - 2021-09-07
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
Improvements towards reducing dependency and coverage processing time (#26)
|
6
|
+
|
7
|
+
## [0.6.1] - 2021-09-06
|
8
|
+
|
9
|
+
### Fixed
|
10
|
+
|
11
|
+
Bug in time formatter (#24)
|
12
|
+
|
13
|
+
### Added
|
14
|
+
|
15
|
+
Environment variable to control verbose output (#25)
|
16
|
+
|
17
|
+
## [0.6.0] - 2021-09-05
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- Improved dependency change detection (#18)
|
22
|
+
- Flaky tests detection (#19)
|
23
|
+
- Exclude vendor files from analysis (#21)
|
24
|
+
- Report elapsed time at various stages (#23)
|
25
|
+
|
26
|
+
### Note
|
27
|
+
|
28
|
+
The first run on this version will not use any cache on the CI because the number
|
29
|
+
of files changed from eight to nine, so there will be no appropriate cache to use.
|
30
|
+
|
31
|
+
## [0.5.0] - 2021-09-03
|
32
|
+
|
33
|
+
### Fixed
|
34
|
+
|
35
|
+
- Limit number of cached files download (#16)
|
36
|
+
|
1
37
|
## [0.4.0] - 2021-09-03
|
2
38
|
|
3
39
|
### Added
|
data/README.md
CHANGED
@@ -8,7 +8,11 @@ It uses [Ruby's built-in coverage library](https://ruby-doc.org/stdlib/libdoc/co
|
|
8
8
|
to keep track of the coverage for each test. For each test executed, the coverage
|
9
9
|
diff provides the desired file list. RSpec Tracer takes care of reporting the
|
10
10
|
**correct code coverage when skipping tests** by using the cached reports. Also,
|
11
|
-
note that it will **never skip
|
11
|
+
note that it will **never skip**:
|
12
|
+
|
13
|
+
- **Flaky examples**
|
14
|
+
- **Failed examples**
|
15
|
+
- **Pending examples**
|
12
16
|
|
13
17
|
Knowing the examples and files dependency gives us a better insight into the codebase,
|
14
18
|
and we have **a clear idea of what to test for when making any changes**. With this data,
|
@@ -16,9 +20,10 @@ we can also analyze the coupling between different components and much more.
|
|
16
20
|
|
17
21
|
## Note
|
18
22
|
|
19
|
-
You should take some time and go through the [document](./RSPEC_TRACER.md) describing
|
20
|
-
the **intention** and implementation details of **
|
21
|
-
and **caching on CI
|
23
|
+
You should take some time and go through the **[document](./RSPEC_TRACER.md)** describing
|
24
|
+
the **intention** and implementation details of **managing dependency**, **managing flaky tests**,
|
25
|
+
**skipping tests**, and **caching on CI**. You must go through the README file before
|
26
|
+
integrating the gem into your project to better understand what is happening.
|
22
27
|
|
23
28
|
## Table of Contents
|
24
29
|
|
@@ -28,15 +33,18 @@ and **caching on CI**, etc.
|
|
28
33
|
* [Additional Tools](#additional-tools)
|
29
34
|
* [Getting Started](#getting-started)
|
30
35
|
* [Environment Variables](#environment-variables)
|
36
|
+
* [BUNDLE_PATH](#bundle_path)
|
31
37
|
* [CI](#ci)
|
32
38
|
* [LOCAL_AWS](#local_aws)
|
33
39
|
* [RSPEC_TRACER_NO_SKIP](#rspec_tracer_no_skip)
|
34
40
|
* [RSPEC_TRACER_S3_URI](#rspec_tracer_s3_uri)
|
35
41
|
* [RSPEC_TRACER_UPLOAD_LOCAL_CACHE](#rspec_tracer_upload_local_cache)
|
36
|
-
* [
|
42
|
+
* [RSPEC_TRACER_VERBOSE](#rspec_tracer_verbose)
|
37
43
|
* [TEST_SUITES](#test_suites)
|
44
|
+
* [TEST_SUITE_ID](#test_suite_id)
|
38
45
|
* [Sample Reports](#sample-reports)
|
39
46
|
* [Examples](#examples)
|
47
|
+
* [Flaky Examples](#flaky-examples)
|
40
48
|
* [Examples Dependency](#examples-dependency)
|
41
49
|
* [Files Dependency](#files-dependency)
|
42
50
|
* [Configuring RSpec Tracer](#configuring-rspec-tracer)
|
@@ -63,7 +71,7 @@ and **caching on CI**, etc.
|
|
63
71
|
|
64
72
|
Add this line to your `Gemfile` and `bundle install`:
|
65
73
|
```ruby
|
66
|
-
gem 'rspec-tracer', group: :test, require: false
|
74
|
+
gem 'rspec-tracer', version: '~> 0.6', group: :test, require: false
|
67
75
|
```
|
68
76
|
|
69
77
|
And, add the followings to your `.gitignore`:
|
@@ -128,13 +136,13 @@ Rakefile in your project to have the following:
|
|
128
136
|
```
|
129
137
|
3. Before running tests, download the remote cache using the following rake task:
|
130
138
|
|
131
|
-
```
|
139
|
+
```sh
|
132
140
|
bundle exec rake rspec_tracer:remote_cache:download
|
133
141
|
```
|
134
142
|
4. Run the tests with RSpec using `bundle exec rspec`.
|
135
143
|
5. After running tests, upload the local cache using the following rake task:
|
136
144
|
|
137
|
-
```
|
145
|
+
```sh
|
138
146
|
bundle exec rake rspec_tracer:remote_cache:upload
|
139
147
|
```
|
140
148
|
6. After running your tests, open `rspec_tracer_report/index.html` in the
|
@@ -142,7 +150,15 @@ browser of your choice.
|
|
142
150
|
|
143
151
|
## Environment Variables
|
144
152
|
|
145
|
-
To get better control on execution, you can use the following
|
153
|
+
To get better control on execution, you can use the following environment variables
|
154
|
+
whenever required.
|
155
|
+
|
156
|
+
### BUNDLE_PATH
|
157
|
+
|
158
|
+
Since the bundler uses a vendor directory inside the project, it might cause slowness
|
159
|
+
depending on the vendor size. You can configure the bundle path outside of the project
|
160
|
+
using `BUNDLE_PATH` environment variable, for example, `BUNDLE_PATH=$HOME/vendor/bundle`.
|
161
|
+
Make sure to cache this directory in the CI configuration.
|
146
162
|
|
147
163
|
### CI
|
148
164
|
|
@@ -177,11 +193,28 @@ export RSPEC_TRACER_S3_URI=s3://ci-artifacts-bucket/rspec-tracer-cache
|
|
177
193
|
By default, RSpec Tracer does not upload local cache files. You can set this
|
178
194
|
environment variable to `true` to upload the local cache to S3.
|
179
195
|
|
196
|
+
### RSPEC_TRACER_VERBOSE
|
197
|
+
|
198
|
+
To print the intermediate steps and time taken, use this environment variable:
|
199
|
+
|
200
|
+
```sh
|
201
|
+
export RSPEC_TRACER_VERBOSE=true
|
202
|
+
```
|
203
|
+
|
204
|
+
### TEST_SUITES
|
205
|
+
|
206
|
+
Set this environment variable when using test suite id. It determines the total
|
207
|
+
number of different test suites you are running.
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
export TEST_SUITES=8
|
211
|
+
```
|
212
|
+
|
180
213
|
### TEST_SUITE_ID
|
181
214
|
|
182
215
|
If you have a large set of tests to run, it is recommended to run them in
|
183
216
|
separate groups. This way, RSpec Tracer is not overwhelmed with loading massive
|
184
|
-
cached data in the memory. Also, it
|
217
|
+
cached data in the memory. Also, it generates and uses cache for specific test suites
|
185
218
|
and not merge them.
|
186
219
|
|
187
220
|
```ruby
|
@@ -189,13 +222,19 @@ TEST_SUITE_ID=1 bundle exec rspec spec/models
|
|
189
222
|
TEST_SUITE_ID=2 bundle exec rspec spec/helpers
|
190
223
|
```
|
191
224
|
|
192
|
-
|
225
|
+
If you run parallel builds on the CI, you should specify the test suite ID and
|
226
|
+
the total number of test suites when downloading the cache files.
|
193
227
|
|
194
|
-
|
195
|
-
|
228
|
+
```sh
|
229
|
+
$ TEST_SUITES=5 TEST_SUITE_ID=1 bundle exec rake rspec_tracer:remote_cache:download
|
230
|
+
```
|
196
231
|
|
197
|
-
|
198
|
-
|
232
|
+
In this case, the appropriate cache should have all the cache files available on
|
233
|
+
the S3 for each test suite, not just for the current one. Also, while uploading,
|
234
|
+
make sure to provide the test suite id.
|
235
|
+
|
236
|
+
```sh
|
237
|
+
$ TEST_SUITE_ID=1 bundle exec rake rspec_tracer:remote_cache:upload
|
199
238
|
```
|
200
239
|
|
201
240
|
## Sample Reports
|
@@ -214,6 +253,19 @@ These reports provide basic test information:
|
|
214
253
|
|
215
254
|

|
216
255
|
|
256
|
+
### Flaky Examples
|
257
|
+
|
258
|
+
These reports provide flaky tests information. Assuming **the following two tests
|
259
|
+
failed in the first run.**
|
260
|
+
|
261
|
+
**Next Run**
|
262
|
+
|
263
|
+

|
264
|
+
|
265
|
+
**Another Run**
|
266
|
+
|
267
|
+

|
268
|
+
|
217
269
|
### Examples Dependency
|
218
270
|
|
219
271
|
These reports show a list of dependent files for each test.
|
data/lib/rspec_tracer/cache.rb
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
module RSpecTracer
|
4
4
|
class Cache
|
5
|
-
attr_reader :all_examples, :
|
5
|
+
attr_reader :all_examples, :flaky_examples, :failed_examples, :pending_examples,
|
6
|
+
:all_files, :dependency, :run_id
|
6
7
|
|
7
8
|
def initialize
|
8
9
|
@run_id = last_run_id
|
@@ -11,6 +12,7 @@ module RSpecTracer
|
|
11
12
|
@cached = false
|
12
13
|
|
13
14
|
@all_examples = {}
|
15
|
+
@flaky_examples = Set.new
|
14
16
|
@failed_examples = Set.new
|
15
17
|
@pending_examples = Set.new
|
16
18
|
@all_files = {}
|
@@ -20,22 +22,36 @@ module RSpecTracer
|
|
20
22
|
def load_cache_for_run
|
21
23
|
return if @run_id.nil? || @cached
|
22
24
|
|
25
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
26
|
+
|
23
27
|
load_all_examples_cache
|
28
|
+
load_flaky_examples_cache
|
24
29
|
load_failed_examples_cache
|
25
30
|
load_pending_examples_cache
|
26
31
|
load_all_files_cache
|
27
32
|
load_dependency_cache
|
28
33
|
|
34
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
35
|
+
|
29
36
|
@cached = true
|
30
37
|
|
31
|
-
|
38
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
39
|
+
|
40
|
+
puts "RSpec tracer loaded cache from #{@cache_dir} (took #{elpased})"
|
32
41
|
end
|
33
42
|
|
34
43
|
def cached_examples_coverage
|
35
44
|
return @examples_coverage if defined?(@examples_coverage)
|
36
45
|
return @examples_coverage = {} if @run_id.nil?
|
37
46
|
|
38
|
-
|
47
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
48
|
+
coverage = load_examples_coverage_cache
|
49
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
50
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
51
|
+
|
52
|
+
puts "RSpec tracer loaded cached examples coverage (took #{elpased})" if RSpecTracer.verbose?
|
53
|
+
|
54
|
+
coverage
|
39
55
|
end
|
40
56
|
|
41
57
|
private
|
@@ -64,6 +80,14 @@ module RSpecTracer
|
|
64
80
|
end
|
65
81
|
end
|
66
82
|
|
83
|
+
def load_flaky_examples_cache
|
84
|
+
file_name = File.join(@cache_dir, 'flaky_examples.json')
|
85
|
+
|
86
|
+
return unless File.file?(file_name)
|
87
|
+
|
88
|
+
@flaky_examples = JSON.parse(File.read(file_name)).to_set
|
89
|
+
end
|
90
|
+
|
67
91
|
def load_failed_examples_cache
|
68
92
|
file_name = File.join(@cache_dir, 'failed_examples.json')
|
69
93
|
|
@@ -102,6 +102,12 @@ module RSpecTracer
|
|
102
102
|
@coverage_filters ||= []
|
103
103
|
end
|
104
104
|
|
105
|
+
def verbose?
|
106
|
+
return @verbose if defined?(@verbose)
|
107
|
+
|
108
|
+
@verbose = ENV.fetch('RSPEC_TRACER_VERBOSE', 'false') == 'true'
|
109
|
+
end
|
110
|
+
|
105
111
|
def configure(&block)
|
106
112
|
Docile.dsl_eval(self, &block)
|
107
113
|
end
|
@@ -29,11 +29,15 @@ module RSpecTracer
|
|
29
29
|
|
30
30
|
def compute_diff(example_id)
|
31
31
|
peek_coverage.each_pair do |file_path, current_stats|
|
32
|
-
|
33
|
-
existing_file_diff_coverage(example_id, file_path, current_stats)
|
34
|
-
else
|
32
|
+
unless @coverage.key?(file_path)
|
35
33
|
missing_file_diff_coverage(example_id, file_path, current_stats)
|
34
|
+
|
35
|
+
next
|
36
36
|
end
|
37
|
+
|
38
|
+
next if current_stats == @coverage[file_path]
|
39
|
+
|
40
|
+
existing_file_diff_coverage(example_id, file_path, current_stats)
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
@@ -6,21 +6,22 @@ require 'time'
|
|
6
6
|
module RSpecTracer
|
7
7
|
module HTMLReporter
|
8
8
|
class Reporter
|
9
|
-
attr_reader :last_run, :examples, :examples_dependency, :files_dependency
|
9
|
+
attr_reader :last_run, :examples, :flaky_examples, :examples_dependency, :files_dependency
|
10
10
|
|
11
11
|
def initialize
|
12
12
|
@reporter = RSpecTracer.runner.reporter
|
13
13
|
|
14
14
|
format_last_run
|
15
15
|
format_examples
|
16
|
+
format_flaky_examples
|
16
17
|
format_examples_dependency
|
17
18
|
format_files_dependency
|
18
19
|
end
|
19
20
|
|
20
21
|
def generate_report
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
23
|
+
|
24
|
+
copy_assets
|
24
25
|
|
25
26
|
file_name = File.join(RSpecTracer.report_path, 'index.html')
|
26
27
|
|
@@ -28,11 +29,20 @@ module RSpecTracer
|
|
28
29
|
file.puts(template('layout').result(binding))
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
33
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
34
|
+
|
35
|
+
puts "RSpecTracer generated HTML report to #{file_name} (took #{elpased})"
|
32
36
|
end
|
33
37
|
|
34
38
|
private
|
35
39
|
|
40
|
+
def copy_assets
|
41
|
+
Dir[File.join(File.dirname(__FILE__), 'public/*')].each do |path|
|
42
|
+
FileUtils.cp_r(path, asset_output_path)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
36
46
|
def format_last_run
|
37
47
|
@last_run = @reporter.last_run.slice(
|
38
48
|
:actual_count,
|
@@ -57,6 +67,10 @@ module RSpecTracer
|
|
57
67
|
end
|
58
68
|
end
|
59
69
|
|
70
|
+
def format_flaky_examples
|
71
|
+
@flaky_examples = @examples.slice(*@reporter.flaky_examples).values
|
72
|
+
end
|
73
|
+
|
60
74
|
def example_run_local_time(utc_time)
|
61
75
|
case utc_time
|
62
76
|
when Time
|
@@ -128,6 +142,14 @@ module RSpecTracer
|
|
128
142
|
template(title_id).result(current_binding)
|
129
143
|
end
|
130
144
|
|
145
|
+
def formatted_flaky_examples(title, flaky_examples)
|
146
|
+
title_id = report_container_id(title)
|
147
|
+
current_binding = binding
|
148
|
+
|
149
|
+
current_binding.local_variable_set(:title_id, title_id)
|
150
|
+
template(title_id).result(current_binding)
|
151
|
+
end
|
152
|
+
|
131
153
|
def formatted_examples_dependency(title, examples_dependency)
|
132
154
|
title_id = report_container_id(title)
|
133
155
|
current_binding = binding
|
@@ -154,7 +176,7 @@ module RSpecTracer
|
|
154
176
|
|
155
177
|
def example_status_css_class(example_status)
|
156
178
|
case example_status.split.first
|
157
|
-
when 'Failed'
|
179
|
+
when 'Failed', 'Flaky'
|
158
180
|
'red'
|
159
181
|
when 'Pending'
|
160
182
|
'yellow'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<div class="report_container" id="<%= title_id %>">
|
2
|
+
<h2>
|
3
|
+
<span class="group_name"><%= title %></span>
|
4
|
+
(
|
5
|
+
<span class="blue">
|
6
|
+
<strong><%= flaky_examples.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>ID</th>
|
18
|
+
<th>Description</th>
|
19
|
+
<th>Location</th>
|
20
|
+
<th>Result</th>
|
21
|
+
<th>Run At</th>
|
22
|
+
</tr>
|
23
|
+
</thead>
|
24
|
+
|
25
|
+
<tbody>
|
26
|
+
<% flaky_examples.each do |example| %>
|
27
|
+
<tr>
|
28
|
+
<td><%= example[:id] %></td>
|
29
|
+
<td><%= example[:description] %></td>
|
30
|
+
<td><%= example[:location] %></td>
|
31
|
+
<td><strong class="<%= example_result_css_class(example[:result]) %>"><%= example[:result] %></strong></td>
|
32
|
+
<td width="8%"><%= example[:last_run] %></td>
|
33
|
+
</tr>
|
34
|
+
<% end %>
|
35
|
+
</tbody>
|
36
|
+
</table>
|
37
|
+
</div>
|
38
|
+
</div>
|
@@ -19,6 +19,9 @@
|
|
19
19
|
|
20
20
|
<div id="content">
|
21
21
|
<%= formatted_examples('Examples', examples.values) %>
|
22
|
+
<% unless flaky_examples.empty? %>
|
23
|
+
<%= formatted_flaky_examples('Flaky Examples', flaky_examples) %>
|
24
|
+
<% end %>
|
22
25
|
<%= formatted_examples_dependency('Examples Dependency', examples_dependency) %>
|
23
26
|
<%= formatted_files_dependency("Files Dependency", files_dependency) %>
|
24
27
|
</div>
|
@@ -20,9 +20,6 @@ module RSpecTracer
|
|
20
20
|
else
|
21
21
|
'aws'
|
22
22
|
end
|
23
|
-
@test_suite_id = ENV['TEST_SUITE_ID'].to_s
|
24
|
-
@test_suites = ENV.fetch('TEST_SUITES', '1').to_i
|
25
|
-
@total_objects = CACHE_FILES_PER_TEST_SUITE * @test_suites
|
26
23
|
end
|
27
24
|
|
28
25
|
def download
|
@@ -32,10 +29,7 @@ module RSpecTracer
|
|
32
29
|
return
|
33
30
|
end
|
34
31
|
|
35
|
-
|
36
|
-
@git.prepare_for_download
|
37
|
-
|
38
|
-
@cache_sha = nearest_cache_sha
|
32
|
+
prepare_for_download
|
39
33
|
|
40
34
|
if @cache_sha.nil?
|
41
35
|
puts 'Could not find a suitable cache sha to download'
|
@@ -57,9 +51,7 @@ module RSpecTracer
|
|
57
51
|
return
|
58
52
|
end
|
59
53
|
|
60
|
-
|
61
|
-
@git = RSpecTracer::RemoteCache::Git.new
|
62
|
-
|
54
|
+
prepare_for_upload
|
63
55
|
upload_files
|
64
56
|
|
65
57
|
puts "Uploaded cache from #{@upload_path} to #{@upload_prefix}"
|
@@ -69,20 +61,57 @@ module RSpecTracer
|
|
69
61
|
|
70
62
|
private
|
71
63
|
|
64
|
+
def prepare_for_download
|
65
|
+
@test_suite_id = ENV['TEST_SUITE_ID']
|
66
|
+
@test_suites = ENV['TEST_SUITES']
|
67
|
+
|
68
|
+
if @test_suite_id.nil? ^ @test_suites.nil?
|
69
|
+
raise(
|
70
|
+
CacheDownloadError,
|
71
|
+
'Both the enviornment variables TEST_SUITE_ID and TEST_SUITES are not set'
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
@git = RSpecTracer::RemoteCache::Git.new
|
76
|
+
@git.prepare_for_download
|
77
|
+
|
78
|
+
generate_cached_files_count_and_regex
|
79
|
+
|
80
|
+
@cache_sha = nearest_cache_sha
|
81
|
+
end
|
82
|
+
|
83
|
+
def generate_cached_files_count_and_regex
|
84
|
+
if @test_suites.nil?
|
85
|
+
@last_run_files_count = 1
|
86
|
+
@last_run_files_regex = '/%<ref>s/last_run.json$'
|
87
|
+
@cached_files_count = CACHE_FILES_PER_TEST_SUITE
|
88
|
+
@cached_files_regex = '/%<ref>s/[0-9a-f]{32}/.+.json'
|
89
|
+
else
|
90
|
+
@test_suites = @test_suites.to_i
|
91
|
+
@test_suites_regex = (1..@test_suites).to_a.join('|')
|
92
|
+
|
93
|
+
@last_run_files_count = @test_suites
|
94
|
+
@last_run_files_regex = "/%<ref>s/(#{@test_suites_regex})/last_run.json$"
|
95
|
+
@cached_files_count = CACHE_FILES_PER_TEST_SUITE * @test_suites.to_i
|
96
|
+
@cached_files_regex = "/%<ref>s/(#{@test_suites_regex})/[0-9a-f]{32}/.+.json$"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
72
100
|
def nearest_cache_sha
|
73
101
|
@git.ref_list.detect do |ref|
|
74
|
-
prefix = "#{@s3_uri}/#{ref}
|
102
|
+
prefix = "#{@s3_uri}/#{ref}/"
|
75
103
|
|
76
104
|
puts "Testing prefix #{prefix}"
|
77
105
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
COMMAND
|
106
|
+
objects = `#{@aws_s3} s3 ls #{prefix} --recursive`.chomp.split("\n")
|
107
|
+
|
108
|
+
last_run_regex = Regexp.new(format(@last_run_files_regex, ref: ref))
|
109
|
+
|
110
|
+
next if objects.count { |object| object.match?(last_run_regex) } != @last_run_files_count
|
84
111
|
|
85
|
-
|
112
|
+
cache_regex = Regexp.new(format(@cached_files_regex, ref: ref))
|
113
|
+
|
114
|
+
objects.count { |object| object.match?(cache_regex) } == @cached_files_count
|
86
115
|
end
|
87
116
|
end
|
88
117
|
|
@@ -90,10 +119,19 @@ module RSpecTracer
|
|
90
119
|
@download_prefix = "#{@s3_uri}/#{@cache_sha}/#{@test_suite_id}/".sub(%r{/+$}, '/')
|
91
120
|
@download_path = RSpecTracer.cache_path
|
92
121
|
|
93
|
-
|
122
|
+
raise CacheDownloadError, 'Failed to download cache files' unless system(
|
94
123
|
@aws_s3, 's3', 'cp',
|
95
|
-
@download_prefix,
|
124
|
+
File.join(@download_prefix, 'last_run.json'),
|
96
125
|
@download_path,
|
126
|
+
out: File::NULL, err: File::NULL
|
127
|
+
)
|
128
|
+
|
129
|
+
@run_id = last_run_id
|
130
|
+
|
131
|
+
return if system(
|
132
|
+
@aws_s3, 's3', 'cp',
|
133
|
+
File.join(@download_prefix, @run_id),
|
134
|
+
File.join(@download_path, @run_id),
|
97
135
|
'--recursive',
|
98
136
|
out: File::NULL, err: File::NULL
|
99
137
|
)
|
@@ -103,22 +141,20 @@ module RSpecTracer
|
|
103
141
|
raise CacheDownloadError, 'Failed to download cache files'
|
104
142
|
end
|
105
143
|
|
106
|
-
def
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
144
|
+
def prepare_for_upload
|
145
|
+
@git = RSpecTracer::RemoteCache::Git.new
|
146
|
+
@test_suite_id = ENV['TEST_SUITE_ID']
|
147
|
+
@upload_prefix = if @test_suite_id.nil?
|
148
|
+
"#{@s3_uri}/#{@git.branch_ref}/"
|
149
|
+
else
|
150
|
+
"#{@s3_uri}/#{@git.branch_ref}/#{@test_suite_id}/"
|
151
|
+
end
|
114
152
|
|
115
|
-
|
153
|
+
@upload_path = RSpecTracer.cache_path
|
154
|
+
@run_id = last_run_id
|
116
155
|
end
|
117
156
|
|
118
157
|
def upload_files
|
119
|
-
@upload_prefix = "#{@s3_uri}/#{@git.branch_ref}/#{@test_suite_id}/".sub(%r{/+$}, '/')
|
120
|
-
@upload_path = RSpecTracer.cache_path
|
121
|
-
|
122
158
|
return if system(
|
123
159
|
@aws_s3, 's3', 'cp',
|
124
160
|
File.join(@upload_path, 'last_run.json'),
|
@@ -127,13 +163,25 @@ module RSpecTracer
|
|
127
163
|
) && system(
|
128
164
|
@aws_s3, 's3', 'cp',
|
129
165
|
File.join(@upload_path, @run_id),
|
130
|
-
|
166
|
+
File.join(@upload_prefix, @run_id),
|
131
167
|
'--recursive',
|
132
168
|
out: File::NULL, err: File::NULL
|
133
169
|
)
|
134
170
|
|
135
171
|
raise CacheUploadError, 'Failed to upload cache files'
|
136
172
|
end
|
173
|
+
|
174
|
+
def last_run_id
|
175
|
+
file_name = File.join(RSpecTracer.cache_path, 'last_run.json')
|
176
|
+
|
177
|
+
return unless File.file?(file_name)
|
178
|
+
|
179
|
+
run_id = JSON.parse(File.read(file_name))['run_id']
|
180
|
+
|
181
|
+
raise LocalCacheNotFoundError, 'Could not find any local cache to upload' if run_id.nil?
|
182
|
+
|
183
|
+
run_id
|
184
|
+
end
|
137
185
|
end
|
138
186
|
end
|
139
187
|
end
|
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
module RSpecTracer
|
4
4
|
class Reporter
|
5
|
-
attr_reader :all_examples, :
|
6
|
-
:
|
5
|
+
attr_reader :all_examples, :possibly_flaky_examples, :flaky_examples, :pending_examples,
|
6
|
+
:all_files, :modified_files, :deleted_files, :dependency, :reverse_dependency,
|
7
|
+
:examples_coverage, :last_run
|
7
8
|
|
8
9
|
def initialize
|
9
10
|
initialize_examples
|
@@ -21,6 +22,7 @@ module RSpecTracer
|
|
21
22
|
end
|
22
23
|
|
23
24
|
def on_example_passed(example_id, result)
|
25
|
+
@passed_examples << example_id
|
24
26
|
@all_examples[example_id][:execution_result] = formatted_execution_result(result)
|
25
27
|
end
|
26
28
|
|
@@ -44,6 +46,14 @@ module RSpecTracer
|
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
49
|
+
def register_possibly_flaky_example(example_id)
|
50
|
+
@possibly_flaky_examples << example_id
|
51
|
+
end
|
52
|
+
|
53
|
+
def register_flaky_example(example_id)
|
54
|
+
@flaky_examples << example_id
|
55
|
+
end
|
56
|
+
|
47
57
|
def register_failed_example(example_id)
|
48
58
|
@failed_examples << example_id
|
49
59
|
end
|
@@ -52,6 +62,10 @@ module RSpecTracer
|
|
52
62
|
@pending_examples << example_id
|
53
63
|
end
|
54
64
|
|
65
|
+
def example_passed?(example_id)
|
66
|
+
@passed_examples.include?(example_id)
|
67
|
+
end
|
68
|
+
|
55
69
|
def example_skipped?(example_id)
|
56
70
|
@skipped_examples.include?(example_id)
|
57
71
|
end
|
@@ -126,6 +140,8 @@ module RSpecTracer
|
|
126
140
|
end
|
127
141
|
|
128
142
|
def write_reports
|
143
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
144
|
+
|
129
145
|
@run_id = Digest::MD5.hexdigest(@all_examples.keys.sort.to_json)
|
130
146
|
@cache_dir = File.join(RSpecTracer.cache_path, @run_id)
|
131
147
|
|
@@ -133,6 +149,7 @@ module RSpecTracer
|
|
133
149
|
|
134
150
|
%i[
|
135
151
|
all_examples
|
152
|
+
flaky_examples
|
136
153
|
failed_examples
|
137
154
|
pending_examples
|
138
155
|
all_files
|
@@ -142,13 +159,19 @@ module RSpecTracer
|
|
142
159
|
last_run
|
143
160
|
].each { |report_type| send("write_#{report_type}_report") }
|
144
161
|
|
145
|
-
|
162
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
163
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
164
|
+
|
165
|
+
puts "RSpec tracer reports written to #{@cache_dir} (took #{elpased})"
|
146
166
|
end
|
147
167
|
|
148
168
|
private
|
149
169
|
|
150
170
|
def initialize_examples
|
151
171
|
@all_examples = {}
|
172
|
+
@passed_examples = Set.new
|
173
|
+
@possibly_flaky_examples = Set.new
|
174
|
+
@flaky_examples = Set.new
|
152
175
|
@failed_examples = Set.new
|
153
176
|
@skipped_examples = Set.new
|
154
177
|
@pending_examples = Set.new
|
@@ -209,6 +232,12 @@ module RSpecTracer
|
|
209
232
|
File.write(file_name, JSON.pretty_generate(@all_examples))
|
210
233
|
end
|
211
234
|
|
235
|
+
def write_flaky_examples_report
|
236
|
+
file_name = File.join(@cache_dir, 'flaky_examples.json')
|
237
|
+
|
238
|
+
File.write(file_name, JSON.pretty_generate(@flaky_examples.to_a))
|
239
|
+
end
|
240
|
+
|
212
241
|
def write_failed_examples_report
|
213
242
|
file_name = File.join(@cache_dir, 'failed_examples.json')
|
214
243
|
|
@@ -2,24 +2,29 @@
|
|
2
2
|
|
3
3
|
module RSpecTracer
|
4
4
|
module RSpecRunner
|
5
|
+
# rubocop:disable Metrics/AbcSize
|
5
6
|
def run_specs(_example_groups)
|
6
7
|
actual_count = RSpec.world.example_count
|
8
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
7
9
|
filtered_examples, example_groups = RSpecTracer.filter_examples
|
8
10
|
|
9
11
|
RSpec.world.instance_variable_set(:@filtered_examples, filtered_examples)
|
10
12
|
RSpec.world.instance_variable_set(:@example_groups, example_groups)
|
11
13
|
|
12
14
|
current_count = RSpec.world.example_count
|
15
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
16
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
13
17
|
|
14
18
|
puts
|
15
19
|
puts <<-EXAMPLES.strip.gsub(/\s+/, ' ')
|
16
20
|
RSpec tracer is running #{current_count} examples (actual: #{actual_count},
|
17
|
-
skipped: #{actual_count - current_count})
|
21
|
+
skipped: #{actual_count - current_count}) (took #{elpased})
|
18
22
|
EXAMPLES
|
19
23
|
|
20
24
|
RSpecTracer.running = true
|
21
25
|
|
22
26
|
super(example_groups)
|
23
27
|
end
|
28
|
+
# rubocop:enable Metrics/AbcSize
|
24
29
|
end
|
25
30
|
end
|
data/lib/rspec_tracer/runner.rb
CHANGED
@@ -8,6 +8,7 @@ module RSpecTracer
|
|
8
8
|
EXAMPLE_RUN_REASON = {
|
9
9
|
explicit_run: 'Explicit run',
|
10
10
|
no_cache: 'No cache',
|
11
|
+
flaky_example: 'Flaky example',
|
11
12
|
failed_example: 'Failed previously',
|
12
13
|
pending_example: 'Pending previously',
|
13
14
|
files_changed: 'Files changed'
|
@@ -20,6 +21,8 @@ module RSpecTracer
|
|
20
21
|
@reporter = RSpecTracer::Reporter.new
|
21
22
|
@filtered_examples = {}
|
22
23
|
|
24
|
+
return if @cache.run_id.nil?
|
25
|
+
|
23
26
|
@cache.load_cache_for_run
|
24
27
|
filter_examples_to_run
|
25
28
|
end
|
@@ -87,16 +90,15 @@ module RSpecTracer
|
|
87
90
|
# rubocop:enable Metrics/AbcSize
|
88
91
|
|
89
92
|
def register_dependency(examples_coverage)
|
93
|
+
filtered_files = Set.new
|
94
|
+
|
90
95
|
examples_coverage.each_pair do |example_id, example_coverage|
|
91
96
|
register_example_files_dependency(example_id)
|
92
97
|
|
93
98
|
example_coverage.each_key do |file_path|
|
94
|
-
|
99
|
+
next if filtered_files.include?(file_path)
|
95
100
|
|
96
|
-
|
97
|
-
|
98
|
-
@reporter.register_source_file(source_file)
|
99
|
-
@reporter.register_dependency(example_id, source_file[:file_name])
|
101
|
+
filtered_files << file_path unless register_file_dependency(example_id, file_path)
|
100
102
|
end
|
101
103
|
end
|
102
104
|
|
@@ -127,15 +129,19 @@ module RSpecTracer
|
|
127
129
|
|
128
130
|
def generate_report
|
129
131
|
@reporter.generate_last_run_report
|
132
|
+
generate_examples_status_report
|
130
133
|
|
131
|
-
|
132
|
-
|
134
|
+
%i[all_files all_examples dependency examples_coverage reverse_dependency].each do |report_type|
|
135
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
133
136
|
|
134
|
-
%i[all_files all_examples dependency examples_coverage].each do |report_type|
|
135
137
|
send("generate_#{report_type}_report")
|
138
|
+
|
139
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
140
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
141
|
+
|
142
|
+
puts "RSpec tracer generated #{report_type.to_s.tr('_', ' ')} report (took #{elpased})" if RSpecTracer.verbose?
|
136
143
|
end
|
137
144
|
|
138
|
-
@reporter.generate_reverse_dependency_report
|
139
145
|
@reporter.write_reports
|
140
146
|
end
|
141
147
|
|
@@ -146,54 +152,74 @@ module RSpecTracer
|
|
146
152
|
end
|
147
153
|
|
148
154
|
def filter_examples_to_run
|
149
|
-
|
150
|
-
|
155
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
156
|
+
@changed_files = fetch_changed_files
|
157
|
+
|
158
|
+
filter_by_example_status
|
151
159
|
filter_by_files_changed
|
152
|
-
end
|
153
160
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
161
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
162
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
163
|
+
|
164
|
+
puts "RSpec tracer processed cache (took #{elpased})" if RSpecTracer.verbose?
|
158
165
|
end
|
159
166
|
|
160
|
-
def
|
161
|
-
|
162
|
-
|
163
|
-
|
167
|
+
def filter_by_example_status
|
168
|
+
add_previously_flaky_examples
|
169
|
+
add_previously_failed_examples
|
170
|
+
add_previously_pending_examples
|
164
171
|
end
|
165
172
|
|
166
173
|
def filter_by_files_changed
|
167
174
|
@cache.dependency.each_pair do |example_id, files|
|
168
175
|
next if @filtered_examples.key?(example_id)
|
176
|
+
next if (@changed_files & files).empty?
|
169
177
|
|
170
|
-
|
171
|
-
break if filtered_by_file_changed?(example_id, file_name)
|
172
|
-
end
|
178
|
+
@filtered_examples[example_id] = EXAMPLE_RUN_REASON[:files_changed]
|
173
179
|
end
|
174
180
|
end
|
175
181
|
|
176
|
-
def
|
177
|
-
|
178
|
-
@filtered_examples[example_id] = EXAMPLE_RUN_REASON[:
|
182
|
+
def add_previously_flaky_examples
|
183
|
+
@cache.flaky_examples.each do |example_id|
|
184
|
+
@filtered_examples[example_id] = EXAMPLE_RUN_REASON[:flaky_example]
|
185
|
+
|
186
|
+
next unless (@changed_files & @cache.dependency[example_id]).empty?
|
179
187
|
|
180
|
-
|
188
|
+
@reporter.register_possibly_flaky_example(example_id)
|
181
189
|
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def add_previously_failed_examples
|
193
|
+
@cache.failed_examples.each do |example_id|
|
194
|
+
next if @filtered_examples.key?(example_id)
|
195
|
+
|
196
|
+
@filtered_examples[example_id] = EXAMPLE_RUN_REASON[:failed_example]
|
197
|
+
|
198
|
+
next unless (@changed_files & @cache.dependency[example_id]).empty?
|
182
199
|
|
183
|
-
|
200
|
+
@reporter.register_possibly_flaky_example(example_id)
|
201
|
+
end
|
202
|
+
end
|
184
203
|
|
185
|
-
|
186
|
-
|
204
|
+
def add_previously_pending_examples
|
205
|
+
@cache.pending_examples.each do |example_id|
|
206
|
+
@filtered_examples[example_id] = EXAMPLE_RUN_REASON[:pending_example]
|
207
|
+
end
|
208
|
+
end
|
187
209
|
|
188
|
-
|
210
|
+
def fetch_changed_files
|
211
|
+
@cache.all_files.each_value do |cached_file|
|
212
|
+
file_name = cached_file[:file_name]
|
213
|
+
source_file = RSpecTracer::SourceFile.from_name(file_name)
|
189
214
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
215
|
+
if source_file.nil?
|
216
|
+
@reporter.on_file_deleted(file_name)
|
217
|
+
elsif cached_file[:digest] != source_file[:digest]
|
218
|
+
@reporter.on_file_modified(file_name)
|
219
|
+
end
|
194
220
|
end
|
195
221
|
|
196
|
-
|
222
|
+
@reporter.modified_files | @reporter.deleted_files
|
197
223
|
end
|
198
224
|
|
199
225
|
def generate_untraced_files(trace_point_files)
|
@@ -227,14 +253,34 @@ module RSpecTracer
|
|
227
253
|
end
|
228
254
|
|
229
255
|
def register_example_file_dependency(example_id, file_name)
|
230
|
-
source_file =
|
256
|
+
source_file = RSpecTracer::SourceFile.from_name(file_name)
|
231
257
|
|
232
258
|
@reporter.register_source_file(source_file)
|
233
259
|
@reporter.register_dependency(example_id, file_name)
|
234
260
|
end
|
235
261
|
|
236
|
-
def
|
237
|
-
|
262
|
+
def register_file_dependency(example_id, file_path)
|
263
|
+
source_file = RSpecTracer::SourceFile.from_path(file_path)
|
264
|
+
|
265
|
+
return false if RSpecTracer.filters.any? { |filter| filter.match?(source_file) }
|
266
|
+
|
267
|
+
@reporter.register_source_file(source_file)
|
268
|
+
@reporter.register_dependency(example_id, source_file[:file_name])
|
269
|
+
|
270
|
+
true
|
271
|
+
end
|
272
|
+
|
273
|
+
def generate_examples_status_report
|
274
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
275
|
+
|
276
|
+
generate_flaky_examples_report
|
277
|
+
generate_failed_examples_report
|
278
|
+
generate_pending_examples_report
|
279
|
+
|
280
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
281
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
282
|
+
|
283
|
+
puts "RSpec tracer generated flaky, failed, and pending examples report (took #{elpased})" if RSpecTracer.verbose?
|
238
284
|
end
|
239
285
|
|
240
286
|
def generate_all_files_report
|
@@ -255,6 +301,16 @@ module RSpecTracer
|
|
255
301
|
end
|
256
302
|
end
|
257
303
|
|
304
|
+
def generate_flaky_examples_report
|
305
|
+
@reporter.possibly_flaky_examples.each do |example_id|
|
306
|
+
next if @reporter.example_deleted?(example_id)
|
307
|
+
next unless @cache.flaky_examples.include?(example_id) ||
|
308
|
+
@reporter.example_passed?(example_id)
|
309
|
+
|
310
|
+
@reporter.register_flaky_example(example_id)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
258
314
|
def generate_failed_examples_report
|
259
315
|
@cache.failed_examples.each do |example_id|
|
260
316
|
next if @reporter.example_deleted?(example_id) ||
|
@@ -296,5 +352,9 @@ module RSpecTracer
|
|
296
352
|
end
|
297
353
|
end
|
298
354
|
end
|
355
|
+
|
356
|
+
def generate_reverse_dependency_report
|
357
|
+
@reporter.generate_reverse_dependency_report
|
358
|
+
end
|
299
359
|
end
|
300
360
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecTracer
|
4
|
+
module TimeFormatter
|
5
|
+
DEFAULT_PRECISION = 2
|
6
|
+
SECONDS_PRECISION = 5
|
7
|
+
|
8
|
+
UNITS = {
|
9
|
+
second: 60,
|
10
|
+
minute: 60,
|
11
|
+
hour: 24,
|
12
|
+
day: Float::INFINITY
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
module_function
|
16
|
+
|
17
|
+
def format_time(seconds)
|
18
|
+
return pluralize(format_duration(seconds), 'second') if seconds < 60
|
19
|
+
|
20
|
+
formatted_duration = UNITS.each_pair.with_object([]) do |(unit, count), duration|
|
21
|
+
next unless seconds.positive?
|
22
|
+
|
23
|
+
seconds, remainder = seconds.divmod(count)
|
24
|
+
|
25
|
+
next if remainder.zero?
|
26
|
+
|
27
|
+
duration << pluralize(format_duration(remainder), unit)
|
28
|
+
end
|
29
|
+
|
30
|
+
formatted_duration.reverse.join(' ')
|
31
|
+
end
|
32
|
+
|
33
|
+
def format_duration(duration)
|
34
|
+
return 0 if duration.negative?
|
35
|
+
|
36
|
+
precision = duration < 1 ? SECONDS_PRECISION : DEFAULT_PRECISION
|
37
|
+
|
38
|
+
strip_trailing_zeroes(format("%<duration>0.#{precision}f", duration: duration))
|
39
|
+
end
|
40
|
+
|
41
|
+
def strip_trailing_zeroes(formatted_duration)
|
42
|
+
formatted_duration.sub(/(?:(\..*[^0])0+|\.0+)$/, '\1')
|
43
|
+
end
|
44
|
+
|
45
|
+
def pluralize(duration, unit)
|
46
|
+
if (duration.to_f - 1).abs < Float::EPSILON
|
47
|
+
"#{duration} #{unit}"
|
48
|
+
else
|
49
|
+
"#{duration} #{unit}s"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private_class_method :format_duration, :strip_trailing_zeroes, :pluralize
|
54
|
+
end
|
55
|
+
end
|
data/lib/rspec_tracer/version.rb
CHANGED
data/lib/rspec_tracer.rb
CHANGED
@@ -23,6 +23,7 @@ require_relative 'rspec_tracer/rspec_runner'
|
|
23
23
|
require_relative 'rspec_tracer/ruby_coverage'
|
24
24
|
require_relative 'rspec_tracer/runner'
|
25
25
|
require_relative 'rspec_tracer/source_file'
|
26
|
+
require_relative 'rspec_tracer/time_formatter'
|
26
27
|
require_relative 'rspec_tracer/version'
|
27
28
|
|
28
29
|
module RSpecTracer
|
@@ -184,22 +185,36 @@ module RSpecTracer
|
|
184
185
|
def generate_reports
|
185
186
|
puts 'RSpec tracer is generating reports'
|
186
187
|
|
187
|
-
|
188
|
-
|
188
|
+
process_dependency
|
189
|
+
process_coverage
|
189
190
|
runner.generate_report
|
190
191
|
RSpecTracer::HTMLReporter::Reporter.new.generate_report
|
191
192
|
end
|
192
193
|
|
193
|
-
def
|
194
|
+
def process_dependency
|
195
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
196
|
+
|
194
197
|
runner.register_deleted_examples
|
195
198
|
runner.register_dependency(coverage_reporter.examples_coverage)
|
196
199
|
runner.register_untraced_dependency(@traced_files)
|
200
|
+
|
201
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
202
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
203
|
+
|
204
|
+
puts "RSpec tracer processed dependency (took #{elpased})" if RSpecTracer.verbose?
|
197
205
|
end
|
198
206
|
|
199
|
-
def
|
207
|
+
def process_coverage
|
208
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
209
|
+
|
200
210
|
coverage_reporter.generate_final_examples_coverage
|
201
211
|
coverage_reporter.merge_coverage(runner.generate_missed_coverage)
|
202
212
|
runner.register_examples_coverage(coverage_reporter.examples_coverage)
|
213
|
+
|
214
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
215
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
216
|
+
|
217
|
+
puts "RSpec tracer processed coverage (took #{elpased})" if RSpecTracer.verbose?
|
203
218
|
end
|
204
219
|
|
205
220
|
def run_simplecov_exit_task
|
@@ -213,12 +228,18 @@ module RSpecTracer
|
|
213
228
|
end
|
214
229
|
|
215
230
|
def run_coverage_exit_task
|
231
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
232
|
+
|
216
233
|
coverage_reporter.generate_final_coverage
|
217
234
|
|
218
235
|
file_name = File.join(RSpecTracer.coverage_path, 'coverage.json')
|
219
236
|
|
220
237
|
write_coverage_report(file_name)
|
221
|
-
|
238
|
+
|
239
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
240
|
+
elpased = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
241
|
+
|
242
|
+
print_coverage_stats(file_name, elpased)
|
222
243
|
end
|
223
244
|
|
224
245
|
def write_coverage_report(file_name)
|
@@ -232,12 +253,13 @@ module RSpecTracer
|
|
232
253
|
File.write(file_name, JSON.pretty_generate(report))
|
233
254
|
end
|
234
255
|
|
235
|
-
def print_coverage_stats(file_name)
|
256
|
+
def print_coverage_stats(file_name, elpased)
|
236
257
|
stat = coverage_reporter.coverage_stat
|
237
258
|
|
238
259
|
puts <<-REPORT.strip.gsub(/\s+/, ' ')
|
239
260
|
Coverage report generated for RSpecTracer to #{file_name}. #{stat[:covered_lines]}
|
240
261
|
/ #{stat[:total_lines]} LOC (#{stat[:covered_percent]}%) covered
|
262
|
+
(took #{elpased})
|
241
263
|
REPORT
|
242
264
|
end
|
243
265
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-tracer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abhimanyu Singh
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: docile
|
@@ -90,6 +90,7 @@ files:
|
|
90
90
|
- lib/rspec_tracer/html_reporter/views/examples.erb
|
91
91
|
- lib/rspec_tracer/html_reporter/views/examples_dependency.erb
|
92
92
|
- lib/rspec_tracer/html_reporter/views/files_dependency.erb
|
93
|
+
- lib/rspec_tracer/html_reporter/views/flaky_examples.erb
|
93
94
|
- lib/rspec_tracer/html_reporter/views/layout.erb
|
94
95
|
- lib/rspec_tracer/remote_cache/cache.rb
|
95
96
|
- lib/rspec_tracer/remote_cache/git.rb
|
@@ -99,13 +100,14 @@ files:
|
|
99
100
|
- lib/rspec_tracer/ruby_coverage.rb
|
100
101
|
- lib/rspec_tracer/runner.rb
|
101
102
|
- lib/rspec_tracer/source_file.rb
|
103
|
+
- lib/rspec_tracer/time_formatter.rb
|
102
104
|
- lib/rspec_tracer/version.rb
|
103
105
|
homepage: https://github.com/avmnu-sng/rspec-tracer
|
104
106
|
licenses:
|
105
107
|
- MIT
|
106
108
|
metadata:
|
107
109
|
homepage_uri: https://github.com/avmnu-sng/rspec-tracer
|
108
|
-
source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v0.
|
110
|
+
source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v0.6.2
|
109
111
|
changelog_uri: https://github.com/avmnu-sng/rspec-tracer/blob/main/CHANGELOG.md
|
110
112
|
bug_tracker_uri: https://github.com/avmnu-sng/rspec-tracer/issues
|
111
113
|
post_install_message:
|