rspec-tracer 0.9.0 → 1.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +41 -14
- data/lib/rspec_tracer/cache.rb +76 -36
- data/lib/rspec_tracer/configuration.rb +16 -9
- data/lib/rspec_tracer/coverage_merger.rb +41 -0
- data/lib/rspec_tracer/coverage_reporter.rb +31 -28
- data/lib/rspec_tracer/coverage_writer.rb +58 -0
- data/lib/rspec_tracer/html_reporter/reporter.rb +56 -16
- data/lib/rspec_tracer/html_reporter/views/duplicate_examples.erb +34 -0
- data/lib/rspec_tracer/html_reporter/views/examples.erb +5 -0
- data/lib/rspec_tracer/html_reporter/views/layout.erb +8 -5
- data/lib/rspec_tracer/remote_cache/validator.rb +1 -1
- data/lib/rspec_tracer/report_generator.rb +158 -0
- data/lib/rspec_tracer/report_merger.rb +81 -0
- data/lib/rspec_tracer/report_writer.rb +141 -0
- data/lib/rspec_tracer/reporter.rb +42 -174
- data/lib/rspec_tracer/rspec_runner.rb +2 -4
- data/lib/rspec_tracer/runner.rb +41 -113
- data/lib/rspec_tracer/version.rb +1 -1
- data/lib/rspec_tracer.rb +202 -29
- metadata +23 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9aaeb8a00f4cebc311367e6a61d59526014849df9f7fe890455d64cef16d435f
|
4
|
+
data.tar.gz: b803d5104a0a35f783bdd15b7a43252a1cfddcdd56d9f0a26c5a07bb8c36ef5e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56acd78d6d4bf7b6270e8517a82d0bb1237dd78518a22d1a84a96de29e00c59379092cd725cc276d3f59ca2916306835c38426917747dcaf278e45d611c99389
|
7
|
+
data.tar.gz: a4e026b2c6fdaad653bd48063c9d0b771e7e7bc8876f653cc2e32279930d6a043b546ab1c7a713e12aac934f1f069497decdb5856246d4ee8033e905577be1db
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,31 @@
|
|
1
|
+
## [1.0.0] - 2021-10-21
|
2
|
+
|
3
|
+
### Added
|
4
|
+
|
5
|
+
- [JRuby](https://github.com/jruby/jruby) support
|
6
|
+
- [Parallel Tests](https://github.com/grosser/parallel_tests) support
|
7
|
+
|
8
|
+
### Breaking Changes
|
9
|
+
|
10
|
+
The first run on this version will not use any cache on the CI because the number
|
11
|
+
of files changed from eight to eleven, so there will be no appropriate cache to use.
|
12
|
+
|
13
|
+
## [0.9.3] - 2021-10-03
|
14
|
+
|
15
|
+
Generate reports ignoring duplicate examples (#42)
|
16
|
+
|
17
|
+
## [0.9.2] - 2021-09-30
|
18
|
+
|
19
|
+
### Fixed
|
20
|
+
|
21
|
+
Caches getting corrupted on interrupts (#39)
|
22
|
+
|
23
|
+
## [0.9.1] - 2021-09-23
|
24
|
+
|
25
|
+
### Fixed
|
26
|
+
|
27
|
+
Flaky and failed examples dependency check (#38)
|
28
|
+
|
1
29
|
## [0.9.0] - 2021-09-15
|
2
30
|
|
3
31
|
### Added
|
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|

|
2
2
|
|
3
|
+
[](https://discord.gg/H2G9yWeuRZ)
|
3
4
|
[](https://codeclimate.com/github/avmnu-sng/rspec-tracer/maintainability)
|
4
5
|
[](https://codeclimate.com/github/avmnu-sng/rspec-tracer/test_coverage)
|
5
6
|
[](https://badge.fury.io/rb/rspec-tracer)
|
@@ -28,19 +29,19 @@ recommended to use **simplecov >= 0.12.0**. To use RSpec Tracer **cache on CI**,
|
|
28
29
|
need to have an **S3 bucket** and **[AWS CLI](https://aws.amazon.com/cli/)**
|
29
30
|
installed.
|
30
31
|
|
31
|
-
You should take some time and go through the **[document](./RSPEC_TRACER.md)**
|
32
|
-
describing the **intention** and implementation details of **managing dependency**,
|
33
|
-
**managing flaky tests**, **skipping tests**, and **caching on CI**.
|
32
|
+
> You should take some time and go through the **[document](./RSPEC_TRACER.md)** describing the **intention** and implementation details of **managing dependency**, **managing flaky tests**, **skipping tests**, and **caching on CI**.
|
34
33
|
|
35
34
|
## Table of Contents
|
36
35
|
|
37
36
|
* [Demo](#demo)
|
38
37
|
* [Getting Started](#getting-started)
|
38
|
+
* [Working with JRuby](#working-with-jruby)
|
39
|
+
* [Working with Parallel Tests](#working-with-parallel-tests)
|
39
40
|
* [Configuring CI Caching](#configuring-ci-caching)
|
40
41
|
* [Advanced Configuration](#advanced-configuration)
|
41
42
|
* [Filters](#filters)
|
42
43
|
* [Environment Variables](#environment-variables)
|
43
|
-
* [
|
44
|
+
* [Duplicate Examples](#duplicate-examples)
|
44
45
|
|
45
46
|
## Demo
|
46
47
|
|
@@ -64,6 +65,12 @@ These reports provide basic test information:
|
|
64
65
|
|
65
66
|

|
66
67
|
|
68
|
+
### Duplicate Examples Report
|
69
|
+
|
70
|
+
These reports provide duplicate tests information.
|
71
|
+
|
72
|
+

|
73
|
+
|
67
74
|
### Flaky Examples Report
|
68
75
|
|
69
76
|
These reports provide flaky tests information. Assuming **the following two tests
|
@@ -131,6 +138,28 @@ any of the application code.**
|
|
131
138
|
3. After running your tests, open `rspec_tracer_report/index.html` in the browser
|
132
139
|
of your choice.
|
133
140
|
|
141
|
+
### Working with JRuby
|
142
|
+
|
143
|
+
It is recommend to use **JRuby 9.2.10.0+**. Also, configure it with **`JRUBY_OPTS="--debug -X+O"`**
|
144
|
+
or have the `.jrubyrc` file:
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
debug.fullTrace=true
|
148
|
+
objectspace.enabled=true
|
149
|
+
```
|
150
|
+
|
151
|
+
### Working with Parallel Tests
|
152
|
+
|
153
|
+
The Rspec tracer, by default, supports working with [parallel_tests](https://github.com/grosser/parallel_tests/)
|
154
|
+
gem. It maintains a lock file `/tmp/parallel_tests.lock` to identify the last
|
155
|
+
running process. Usually, you are not required to do anything special unless you
|
156
|
+
interrupt the execution in between and the process did not complete correctly.
|
157
|
+
In such a case, you must delete the lock file before the next run.
|
158
|
+
|
159
|
+
```sh
|
160
|
+
rm -f /tmp/parallel_tests.lock && bundle exec parallel_rspec
|
161
|
+
```
|
162
|
+
|
134
163
|
## Configuring CI Caching
|
135
164
|
|
136
165
|
To enable RSpec Tracer to share cache between different builds on CI, update the
|
@@ -224,11 +253,11 @@ variables:
|
|
224
253
|
```
|
225
254
|
- **`RSPEC_TRACER_COVERAGE_DIR`** to update the default coverage directory (`rspec_tracer_coverage`).
|
226
255
|
```sh
|
227
|
-
export
|
256
|
+
export RSPEC_TRACER_COVERAGE_DIR=/tmp/rspec_tracer_coverage
|
228
257
|
```
|
229
258
|
- **`RSPEC_TRACER_REPORT_DIR`** to update the default html reports directory (`rspec_tracer_report`).
|
230
259
|
```sh
|
231
|
-
export
|
260
|
+
export RSPEC_TRACER_REPORT_DIR=/tmp/rspec_tracer_report
|
232
261
|
```
|
233
262
|
|
234
263
|
These settings are available through environment variables because the rake tasks
|
@@ -309,6 +338,9 @@ development environment. You can install [localstack](https://github.com/localst
|
|
309
338
|
and [awscli-local](https://github.com/localstack/awscli-local) and then invoke the
|
310
339
|
rake tasks with `LOCAL_AWS=true`.
|
311
340
|
|
341
|
+
- **`RSPEC_TRACER_FAIL_ON_DUPLICATES (default: true)`:** By default, RSpec Tracer
|
342
|
+
exits with one if there are [duplicate examples](#duplicate-examples).
|
343
|
+
|
312
344
|
- **`RSPEC_TRACER_NO_SKIP (default: false)`:** Use this environment variables to
|
313
345
|
not skip any tests. Note that it will continue to maintain cache files and generate
|
314
346
|
reports.
|
@@ -335,7 +367,7 @@ specific test suites and not merge them.
|
|
335
367
|
TEST_SUITE_ID=2 bundle exec rspec spec/helpers
|
336
368
|
```
|
337
369
|
|
338
|
-
##
|
370
|
+
## Duplicate Examples
|
339
371
|
|
340
372
|
To uniquely identify the examples is one of the requirements for the correctness
|
341
373
|
of the RSpec Tracer. Sometimes, it would not be possible to do so depending upon
|
@@ -425,17 +457,12 @@ Calculator
|
|
425
457
|
```
|
426
458
|
|
427
459
|
In this scenario, RSpec Tracer cannot determine the `Calculator#add` and
|
428
|
-
`Calculator#sub` group examples.
|
429
|
-
you have made some changes to your spec files.
|
460
|
+
`Calculator#sub` group examples.
|
430
461
|
|
431
462
|
```
|
432
463
|
================================================================================
|
433
|
-
|
464
|
+
IMPORTANT NOTICE -- RSPEC TRACER COULD NOT IDENTIFY SOME EXAMPLES UNIQUELY
|
434
465
|
================================================================================
|
435
|
-
It would be best to make changes so that the RSpec tracer can uniquely
|
436
|
-
identify all the examples, and then you can enable the RSpec tracer back.
|
437
|
-
================================================================================
|
438
|
-
|
439
466
|
RSpec tracer could not uniquely identify the following 10 examples:
|
440
467
|
- Example ID: eabd51a899db4f64d5839afe96004f03 (5 examples)
|
441
468
|
* Calculator#add (spec/calculator_spec.rb:13)
|
data/lib/rspec_tracer/cache.rb
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
|
3
3
|
module RSpecTracer
|
4
4
|
class Cache
|
5
|
-
attr_reader :all_examples, :
|
6
|
-
:
|
5
|
+
attr_reader :all_examples, :duplicate_examples, :interrupted_examples,
|
6
|
+
:flaky_examples, :failed_examples, :pending_examples, :skipped_examples,
|
7
|
+
:all_files, :dependency, :examples_coverage, :run_id
|
7
8
|
|
8
9
|
def initialize
|
9
|
-
@run_id = last_run_id
|
10
|
-
@cache_dir = File.join(RSpecTracer.cache_path, @run_id) if @run_id
|
11
|
-
|
12
10
|
@cached = false
|
13
11
|
|
14
12
|
@all_examples = {}
|
13
|
+
@duplicate_examples = {}
|
14
|
+
@interrupted_examples = Set.new
|
15
15
|
@flaky_examples = Set.new
|
16
16
|
@failed_examples = Set.new
|
17
17
|
@pending_examples = Set.new
|
@@ -20,52 +20,67 @@ module RSpecTracer
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def load_cache_for_run
|
23
|
-
return if @
|
23
|
+
return if @cached
|
24
|
+
|
25
|
+
cache_path = RSpecTracer.cache_path
|
26
|
+
cache_path = File.dirname(cache_path) if RSpecTracer.parallel_tests?
|
27
|
+
run_id = last_run_id(cache_path)
|
28
|
+
|
29
|
+
return if run_id.nil?
|
24
30
|
|
25
31
|
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
32
|
+
cache_dir = File.join(cache_path, run_id)
|
26
33
|
|
27
|
-
load_all_examples_cache
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
load_all_examples_cache(cache_dir)
|
35
|
+
load_duplicate_examples_cache(cache_dir)
|
36
|
+
load_interrupted_examples_cache(cache_dir)
|
37
|
+
load_flaky_examples_cache(cache_dir)
|
38
|
+
load_failed_examples_cache(cache_dir)
|
39
|
+
load_pending_examples_cache(cache_dir)
|
40
|
+
load_all_files_cache(cache_dir)
|
41
|
+
load_dependency_cache(cache_dir)
|
33
42
|
|
34
43
|
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
35
44
|
|
36
45
|
@cached = true
|
37
46
|
|
38
|
-
|
47
|
+
elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
39
48
|
|
40
|
-
puts "RSpec tracer loaded cache from #{
|
49
|
+
puts "RSpec tracer loaded cache from #{cache_dir} (took #{elapsed})"
|
41
50
|
end
|
42
51
|
|
43
52
|
def cached_examples_coverage
|
44
53
|
return @examples_coverage if defined?(@examples_coverage)
|
45
|
-
|
54
|
+
|
55
|
+
cache_path = RSpecTracer.cache_path
|
56
|
+
cache_path = File.dirname(cache_path) if RSpecTracer.parallel_tests?
|
57
|
+
run_id = last_run_id(cache_path)
|
58
|
+
|
59
|
+
return @examples_coverage = {} if run_id.nil?
|
46
60
|
|
47
61
|
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
48
|
-
|
62
|
+
cache_dir = File.join(cache_path, run_id)
|
63
|
+
coverage = load_examples_coverage_cache(cache_dir)
|
49
64
|
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
50
|
-
|
65
|
+
elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
|
51
66
|
|
52
|
-
puts "RSpec tracer loaded cached examples coverage (took #{
|
67
|
+
puts "RSpec tracer loaded cached examples coverage (took #{elapsed})" if RSpecTracer.verbose?
|
53
68
|
|
54
69
|
coverage
|
55
70
|
end
|
56
71
|
|
57
72
|
private
|
58
73
|
|
59
|
-
def last_run_id
|
60
|
-
file_name = File.join(
|
74
|
+
def last_run_id(cache_dir)
|
75
|
+
file_name = File.join(cache_dir, 'last_run.json')
|
61
76
|
|
62
77
|
return unless File.file?(file_name)
|
63
78
|
|
64
79
|
JSON.parse(File.read(file_name))['run_id']
|
65
80
|
end
|
66
81
|
|
67
|
-
def load_all_examples_cache
|
68
|
-
file_name = File.join(
|
82
|
+
def load_all_examples_cache(cache_dir, discard_run_reason: true)
|
83
|
+
file_name = File.join(cache_dir, 'all_examples.json')
|
69
84
|
|
70
85
|
return unless File.file?(file_name)
|
71
86
|
|
@@ -74,38 +89,63 @@ module RSpecTracer
|
|
74
89
|
end
|
75
90
|
|
76
91
|
@all_examples.each_value do |example|
|
77
|
-
example[:execution_result].transform_keys!(&:to_sym)
|
92
|
+
example[:execution_result].transform_keys!(&:to_sym) if example.key?(:execution_result)
|
93
|
+
example[:run_reason] = nil if discard_run_reason
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def load_duplicate_examples_cache(cache_dir)
|
98
|
+
file_name = File.join(cache_dir, 'duplicate_examples.json')
|
99
|
+
|
100
|
+
return unless File.file?(file_name)
|
78
101
|
|
79
|
-
|
102
|
+
@duplicate_examples = JSON.parse(File.read(file_name)).transform_values do |examples|
|
103
|
+
examples.map { |example| example.transform_keys(&:to_sym) }
|
80
104
|
end
|
81
105
|
end
|
82
106
|
|
83
|
-
def
|
84
|
-
file_name = File.join(
|
107
|
+
def load_interrupted_examples_cache(cache_dir)
|
108
|
+
file_name = File.join(cache_dir, 'interrupted_examples.json')
|
109
|
+
|
110
|
+
return unless File.file?(file_name)
|
111
|
+
|
112
|
+
@interrupted_examples = JSON.parse(File.read(file_name)).to_set
|
113
|
+
end
|
114
|
+
|
115
|
+
def load_flaky_examples_cache(cache_dir)
|
116
|
+
file_name = File.join(cache_dir, 'flaky_examples.json')
|
85
117
|
|
86
118
|
return unless File.file?(file_name)
|
87
119
|
|
88
120
|
@flaky_examples = JSON.parse(File.read(file_name)).to_set
|
89
121
|
end
|
90
122
|
|
91
|
-
def load_failed_examples_cache
|
92
|
-
file_name = File.join(
|
123
|
+
def load_failed_examples_cache(cache_dir)
|
124
|
+
file_name = File.join(cache_dir, 'failed_examples.json')
|
93
125
|
|
94
126
|
return unless File.file?(file_name)
|
95
127
|
|
96
128
|
@failed_examples = JSON.parse(File.read(file_name)).to_set
|
97
129
|
end
|
98
130
|
|
99
|
-
def load_pending_examples_cache
|
100
|
-
file_name = File.join(
|
131
|
+
def load_pending_examples_cache(cache_dir)
|
132
|
+
file_name = File.join(cache_dir, 'pending_examples.json')
|
101
133
|
|
102
134
|
return unless File.file?(file_name)
|
103
135
|
|
104
136
|
@pending_examples = JSON.parse(File.read(file_name)).to_set
|
105
137
|
end
|
106
138
|
|
107
|
-
def
|
108
|
-
file_name = File.join(
|
139
|
+
def load_skipped_examples_cache(cache_dir)
|
140
|
+
file_name = File.join(cache_dir, 'skipped_examples.json')
|
141
|
+
|
142
|
+
return unless File.file?(file_name)
|
143
|
+
|
144
|
+
@skipped_examples = JSON.parse(File.read(file_name)).to_set
|
145
|
+
end
|
146
|
+
|
147
|
+
def load_all_files_cache(cache_dir)
|
148
|
+
file_name = File.join(cache_dir, 'all_files.json')
|
109
149
|
|
110
150
|
return unless File.file?(file_name)
|
111
151
|
|
@@ -114,16 +154,16 @@ module RSpecTracer
|
|
114
154
|
end
|
115
155
|
end
|
116
156
|
|
117
|
-
def load_dependency_cache
|
118
|
-
file_name = File.join(
|
157
|
+
def load_dependency_cache(cache_dir)
|
158
|
+
file_name = File.join(cache_dir, 'dependency.json')
|
119
159
|
|
120
160
|
return unless File.file?(file_name)
|
121
161
|
|
122
162
|
@dependency = JSON.parse(File.read(file_name)).transform_values(&:to_set)
|
123
163
|
end
|
124
164
|
|
125
|
-
def load_examples_coverage_cache
|
126
|
-
file_name = File.join(
|
165
|
+
def load_examples_coverage_cache(cache_dir)
|
166
|
+
file_name = File.join(cache_dir, 'examples_coverage.json')
|
127
167
|
|
128
168
|
return unless File.file?(file_name)
|
129
169
|
|
@@ -31,7 +31,8 @@ module RSpecTracer
|
|
31
31
|
def cache_path
|
32
32
|
@cache_path ||= begin
|
33
33
|
cache_path = File.expand_path(cache_dir, root)
|
34
|
-
cache_path = File.join(cache_path, ENV['TEST_SUITE_ID'].to_s)
|
34
|
+
cache_path = File.join(cache_path, ENV['TEST_SUITE_ID'].to_s) if ENV['TEST_SUITE_ID']
|
35
|
+
cache_path = File.join(cache_path, parallel_tests_id) if RSpecTracer.parallel_tests?
|
35
36
|
|
36
37
|
FileUtils.mkdir_p(cache_path)
|
37
38
|
|
@@ -46,7 +47,8 @@ module RSpecTracer
|
|
46
47
|
def report_path
|
47
48
|
@report_path ||= begin
|
48
49
|
report_path = File.expand_path(report_dir, root)
|
49
|
-
report_path = File.join(report_path, ENV['TEST_SUITE_ID'].to_s)
|
50
|
+
report_path = File.join(report_path, ENV['TEST_SUITE_ID'].to_s) if ENV['TEST_SUITE_ID']
|
51
|
+
report_path = File.join(report_path, parallel_tests_id) if RSpecTracer.parallel_tests?
|
50
52
|
|
51
53
|
FileUtils.mkdir_p(report_path)
|
52
54
|
|
@@ -61,7 +63,8 @@ module RSpecTracer
|
|
61
63
|
def coverage_path
|
62
64
|
@coverage_path ||= begin
|
63
65
|
coverage_path = File.expand_path(coverage_dir, root)
|
64
|
-
coverage_path = File.join(coverage_path, ENV['TEST_SUITE_ID'].to_s)
|
66
|
+
coverage_path = File.join(coverage_path, ENV['TEST_SUITE_ID'].to_s) if ENV['TEST_SUITE_ID']
|
67
|
+
coverage_path = File.join(coverage_path, parallel_tests_id) if RSpecTracer.parallel_tests?
|
65
68
|
|
66
69
|
FileUtils.mkdir_p(coverage_path)
|
67
70
|
|
@@ -93,6 +96,10 @@ module RSpecTracer
|
|
93
96
|
@coverage_filters ||= []
|
94
97
|
end
|
95
98
|
|
99
|
+
def parallel_tests_lock_file
|
100
|
+
'/tmp/parallel_tests.lock'
|
101
|
+
end
|
102
|
+
|
96
103
|
def verbose?
|
97
104
|
@verbose ||= (ENV.fetch('RSPEC_TRACER_VERBOSE', 'false') == 'true')
|
98
105
|
end
|
@@ -103,12 +110,12 @@ module RSpecTracer
|
|
103
110
|
|
104
111
|
private
|
105
112
|
|
106
|
-
def
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
113
|
+
def parallel_tests_id
|
114
|
+
if ParallelTests.first_process?
|
115
|
+
'parallel_tests_1'
|
116
|
+
else
|
117
|
+
"parallel_tests_#{ENV['TEST_ENV_NUMBER']}"
|
118
|
+
end
|
112
119
|
end
|
113
120
|
|
114
121
|
def at_exit(&block)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecTracer
|
4
|
+
class CoverageMerger
|
5
|
+
attr_reader :coverage
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@coverage = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def merge(reports_dir)
|
12
|
+
return if RSpecTracer.simplecov?
|
13
|
+
|
14
|
+
reports_dir.each do |report_dir|
|
15
|
+
next unless File.directory?(report_dir)
|
16
|
+
|
17
|
+
cache_coverage = JSON.parse(File.read("#{report_dir}/coverage.json"))['RSpecTracer']['coverage']
|
18
|
+
|
19
|
+
cache_coverage.each_pair do |file_name, line_coverage|
|
20
|
+
unless @coverage.key?(file_name)
|
21
|
+
@coverage[file_name] = line_coverage
|
22
|
+
|
23
|
+
next
|
24
|
+
end
|
25
|
+
|
26
|
+
merge_line_coverage(file_name, line_coverage)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def merge_line_coverage(file_name, line_coverage)
|
34
|
+
line_coverage.each_with_index do |strength, line_number|
|
35
|
+
next unless strength && @coverage[file_name][line_number]
|
36
|
+
|
37
|
+
@coverage[file_name][line_number] += strength
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -88,8 +88,6 @@ module RSpecTracer
|
|
88
88
|
all_files.each do |file_path|
|
89
89
|
@coverage[file_path] ||= line_stub(file_path).freeze
|
90
90
|
end
|
91
|
-
|
92
|
-
generate_final_coverage_stat
|
93
91
|
end
|
94
92
|
|
95
93
|
private
|
@@ -131,31 +129,6 @@ module RSpecTracer
|
|
131
129
|
all_files.sort
|
132
130
|
end
|
133
131
|
|
134
|
-
def generate_final_coverage_stat
|
135
|
-
total_loc = 0
|
136
|
-
covered_loc = 0
|
137
|
-
|
138
|
-
@coverage.each_pair do |_file_path, line_coverage|
|
139
|
-
line_coverage.each do |strength|
|
140
|
-
next if strength.nil?
|
141
|
-
|
142
|
-
total_loc += 1
|
143
|
-
covered_loc += 1 if strength.positive?
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
@coverage_stat = {
|
148
|
-
total_lines: total_loc,
|
149
|
-
covered_lines: covered_loc,
|
150
|
-
missed_lines: total_loc - covered_loc,
|
151
|
-
covered_percent: 0.0
|
152
|
-
}
|
153
|
-
|
154
|
-
return if total_loc.zero?
|
155
|
-
|
156
|
-
@coverage_stat[:covered_percent] = (100.0 * covered_loc / total_loc).round(2)
|
157
|
-
end
|
158
|
-
|
159
132
|
def peek_coverage
|
160
133
|
data = ::Coverage.peek_result.select do |file_path, _|
|
161
134
|
file_path.start_with?(RSpecTracer.root)
|
@@ -167,8 +140,17 @@ module RSpecTracer
|
|
167
140
|
end
|
168
141
|
|
169
142
|
def line_stub(file_path)
|
143
|
+
case RUBY_ENGINE
|
144
|
+
when 'ruby'
|
145
|
+
ruby_line_stub(file_path)
|
146
|
+
when 'jruby'
|
147
|
+
jruby_line_stub(file_path)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def ruby_line_stub(file_path)
|
170
152
|
lines = File.foreach(file_path).map { nil }
|
171
|
-
iseqs = [RubyVM::InstructionSequence.compile_file(file_path)]
|
153
|
+
iseqs = [::RubyVM::InstructionSequence.compile_file(file_path)]
|
172
154
|
|
173
155
|
until iseqs.empty?
|
174
156
|
iseq = iseqs.pop
|
@@ -179,5 +161,26 @@ module RSpecTracer
|
|
179
161
|
|
180
162
|
lines
|
181
163
|
end
|
164
|
+
|
165
|
+
def jruby_line_stub(file_path)
|
166
|
+
lines = File.foreach(file_path).map { nil }
|
167
|
+
root_node = ::JRuby.parse(File.read(file_path))
|
168
|
+
|
169
|
+
visitor = org.jruby.ast.visitor.NodeVisitor.impl do |_name, node|
|
170
|
+
if node.newline?
|
171
|
+
if node.respond_to?(:position)
|
172
|
+
lines[node.position.line] = 0
|
173
|
+
else
|
174
|
+
lines[node.line] = 0
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
node.child_nodes.each { |child| child&.accept(visitor) }
|
179
|
+
end
|
180
|
+
|
181
|
+
root_node.accept(visitor)
|
182
|
+
|
183
|
+
lines
|
184
|
+
end
|
182
185
|
end
|
183
186
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpecTracer
|
4
|
+
class CoverageWriter
|
5
|
+
def initialize(file_name, reporter)
|
6
|
+
@file_name = file_name
|
7
|
+
@reporter = reporter
|
8
|
+
end
|
9
|
+
|
10
|
+
def write_report
|
11
|
+
report = {
|
12
|
+
RSpecTracer: {
|
13
|
+
coverage: @reporter.coverage,
|
14
|
+
timestamp: Time.now.utc.to_i
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
File.write(@file_name, JSON.pretty_generate(report))
|
19
|
+
end
|
20
|
+
|
21
|
+
def print_stats(elapsed_time)
|
22
|
+
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
23
|
+
|
24
|
+
total, covered, percent = coverage_stats
|
25
|
+
|
26
|
+
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
27
|
+
elapsed = RSpecTracer::TimeFormatter.format_time((ending - starting) + elapsed_time)
|
28
|
+
|
29
|
+
puts <<-STATS.strip.gsub(/\s+/, ' ')
|
30
|
+
Coverage report generated for RSpecTracer to #{@file_name}.
|
31
|
+
#{covered} / #{total} LOC (#{percent}%) covered (took #{elapsed})
|
32
|
+
STATS
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def coverage_stats
|
38
|
+
total_loc = 0
|
39
|
+
covered_loc = 0
|
40
|
+
covered_percent = 0.0
|
41
|
+
|
42
|
+
@reporter.coverage.each_pair do |_file_path, line_coverage|
|
43
|
+
line_coverage.each do |strength|
|
44
|
+
next if strength.nil?
|
45
|
+
|
46
|
+
total_loc += 1
|
47
|
+
covered_loc += 1 if strength.positive?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
return [total_loc, covered_loc, covered_percent] if total_loc.zero?
|
52
|
+
|
53
|
+
covered_percent = (100.0 * covered_loc / total_loc).round(2)
|
54
|
+
|
55
|
+
[total_loc, covered_loc, covered_percent]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|