rspec-tracer 1.1.1 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e89ead4a61c2b6d881fe8ca3fbcfec3ab94e7d8b78f759de5fe64e4e22b693d
4
- data.tar.gz: 8993bc089a6185bdd7f0e20f71ad69b13fbd96c679bf67878c69b4f52dacd12f
3
+ metadata.gz: 4ca4bb6bc03d28906d2dd6456af80031a9bc60a9d05ce761d818f4ef01284f40
4
+ data.tar.gz: b2e975d043e9e34a92055312e65be52d41ec34c515fe520b7c063bbde8ddc1eb
5
5
  SHA512:
6
- metadata.gz: 91dc8066a66b706cfc02e9cb1050c61fc2e6bfa15d7353d056b11edb854ac493a99770516ce57e9ef4649494a0d99573b9dc8e441ead128b9e28abce9e2e21c1
7
- data.tar.gz: 21a2cceab3b2cd3f1c3051542a1267d03a7cb499417f9501502a8c1bac3ac62e96821a1c07ea977768adca4b806a1fffca13b0e2946db95f031e877bc5bc4ebd
6
+ metadata.gz: d33f2f0d87397499ce7b72ca197e8496c0f4ceea3ef791a61e74f10db81ed7655d6a0febb72366d24d7931779c305187202d281069fb83194440430112c54fd8
7
+ data.tar.gz: 2bd13872430988cdf0285f06a07db02492adb485c4273b1ac48526bb0419f3d27a56d73e64fa3cb111d436e25016c7cf2de75c0876286e16974971f541f08e98
data/CHANGELOG.md CHANGED
@@ -1,3 +1,75 @@
1
+ ## [1.2.0] - 2026-04-24
2
+
3
+ ### Added
4
+
5
+ - **`USE_TEST_SUITE_ID_CACHE` env flag** for per-suite remote-cache
6
+ validation. When set to the exact string `"true"`, the validator
7
+ accepts the current `TEST_SUITE_ID`'s cache independently of peer
8
+ suites' state — so an aborted suite on one CI run no longer forces
9
+ a cold run across every suite on the next. Default unset preserves
10
+ 1.1.x behaviour byte-for-byte. Ports
11
+ [upstream PR #70](https://github.com/avmnu-sng/rspec-tracer/pull/70)
12
+ (kirpalsangha), with credit to the interviewstreet fork
13
+ ([PR #1](https://github.com/interviewstreet/rspec-tracer/pull/1))
14
+ where the same fix was shipped to HackerRank's production CI in 2024.
15
+
16
+ ### Fixed
17
+
18
+ - **`SourceFile#file_path` silently dropped dependencies on external
19
+ absolute paths** (closes the issue behind upstream PRs
20
+ [#37](https://github.com/avmnu-sng/rspec-tracer/pull/37) and
21
+ [#67](https://github.com/avmnu-sng/rspec-tracer/pull/67)). When a
22
+ spec's `metadata[:file_path]` resolved to an absolute path **outside**
23
+ `RSpecTracer.root` — typical for shared examples from vendored gems
24
+ (`/opt/bundle/gems/rspec-rails-X/lib/shared_examples/...`) or for
25
+ monorepo spec files adjacent to the project tree — the method stripped
26
+ the leading `/` and expanded against `RSpecTracer.root`, producing a
27
+ non-existent path like `<root>/opt/bundle/...`. `File.file?` returned
28
+ false, `from_path` returned nil, and the tracer **silently skipped
29
+ dependency registration** for that file. Cache-staleness silent-
30
+ correctness bug: shared examples from external gems never appeared as
31
+ dependencies, so changes to them never invalidated the cache. The fix
32
+ returns the input unchanged only when it's an absolute path to an
33
+ existing file outside `RSpecTracer.root`; all other inputs (relative
34
+ paths, stripped-root forms, in-root absolute paths) continue through
35
+ the existing project-relative expansion, preserving byte-for-byte
36
+ behaviour for 1.1.x configurations.
37
+ - **`ValidationError` constant now defined.** `remote_cache/validator.rb`
38
+ previously referenced `ValidationError` in its XOR-guard `raise` but
39
+ the constant was never declared anywhere. Tripping the guard
40
+ (setting exactly one of `TEST_SUITE_ID` / `TEST_SUITES`) raised
41
+ `NameError: uninitialized constant` instead of the intended
42
+ error class. Added `class ValidationError < StandardError` inside
43
+ `Validator`, mirroring `Aws::AwsError`.
44
+ - **Typo in the XOR-guard error message** — `"enviornment"` →
45
+ `"environment"`.
46
+ - **Single-suite `@cached_files_regex` now anchored** — `$` appended
47
+ so the pattern no longer matches extraneous-extension files like
48
+ `/ref/hash/foo.json.backup`. Multi-suite regex was already anchored
49
+ in 1.1.x.
50
+ - **`remote_cache/aws.rb#upload_dir` error message** — reported
51
+ `"Failed to download files from …"` when the upload failed. Closes
52
+ upstream [PR #64](https://github.com/avmnu-sng/rspec-tracer/pull/64)
53
+ (C3).
54
+
55
+ ## [1.1.2] - 2026-04-24
56
+
57
+ ### Fixed
58
+
59
+ - **Encoding crash on locale-unset shells** — legacy cache, report, and
60
+ coverage paths called `File.read` / `File.write` without an explicit
61
+ encoding. Ruby fell back to `Encoding.default_external`, which
62
+ resolves to `US-ASCII` on shells launched without `LANG` set (the
63
+ default for macOS GUI terminals and many LaunchAgent contexts). Any
64
+ spec description containing a non-ASCII byte (e.g. `§`, typographic
65
+ quotes) wrote UTF-8 into `all_examples.json`; the next warm run
66
+ crashed at `Cache#load_*_cache` with
67
+ `Encoding::InvalidByteSequenceError: "\xC2" on US-ASCII`, taking
68
+ `spec_helper` down before any example ran. Every legacy JSON / ERB /
69
+ Ruby-source read and write now pins `encoding: 'UTF-8'`;
70
+ `SourceFile.from_path` switches to `File.binread` so the MD5 digest
71
+ hashes raw bytes regardless of process locale.
72
+
1
73
  ## [1.1.1] - 2026-04-23
2
74
 
3
75
  ### Fixed
data/README.md CHANGED
@@ -149,6 +149,22 @@ uses cache for specific test suites and not merge them.
149
149
  TEST_SUITE_ID=2 bundle exec rspec spec/helpers
150
150
  ```
151
151
 
152
+ - **`USE_TEST_SUITE_ID_CACHE`** (optional, default unset) changes how the remote
153
+ cache validator treats per-suite caches. When set to the exact string `"true"`,
154
+ the validator accepts the current `TEST_SUITE_ID`'s cache independently of the
155
+ other suites' state — so if one suite aborts mid-CI, the remaining suites can
156
+ still reuse their own cached results on the next run. When unset (the default),
157
+ 1.1.x behaviour is preserved byte-for-byte: the validator requires every
158
+ `TEST_SUITES` slot to be present before accepting any cache.
159
+ ```sh
160
+ export TEST_SUITES=3
161
+ export TEST_SUITE_ID=1
162
+ export USE_TEST_SUITE_ID_CACHE=true
163
+ bundle exec rspec spec/models
164
+ ```
165
+ Only the literal string `"true"` activates the flag; `"1"`, `"yes"`, or any
166
+ other truthy value keeps the default behaviour.
167
+
152
168
  ## Advanced Configuration
153
169
 
154
170
  Configuration settings must be defined in **`.rspec-tracer`** file:
@@ -98,7 +98,7 @@ module RSpecTracer
98
98
 
99
99
  return unless File.file?(file_name)
100
100
 
101
- JSON.parse(File.read(file_name))['run_id']
101
+ JSON.parse(File.read(file_name, encoding: 'UTF-8'))['run_id']
102
102
  end
103
103
 
104
104
  def load_all_examples_cache(cache_dir, discard_run_reason: true)
@@ -106,7 +106,7 @@ module RSpecTracer
106
106
 
107
107
  return unless File.file?(file_name)
108
108
 
109
- @all_examples = JSON.parse(File.read(file_name)).transform_values do |examples|
109
+ @all_examples = JSON.parse(File.read(file_name, encoding: 'UTF-8')).transform_values do |examples|
110
110
  examples.transform_keys(&:to_sym)
111
111
  end
112
112
 
@@ -121,7 +121,7 @@ module RSpecTracer
121
121
 
122
122
  return unless File.file?(file_name)
123
123
 
124
- @duplicate_examples = JSON.parse(File.read(file_name)).transform_values do |examples|
124
+ @duplicate_examples = JSON.parse(File.read(file_name, encoding: 'UTF-8')).transform_values do |examples|
125
125
  examples.map { |example| example.transform_keys(&:to_sym) }
126
126
  end
127
127
  end
@@ -131,7 +131,7 @@ module RSpecTracer
131
131
 
132
132
  return unless File.file?(file_name)
133
133
 
134
- @interrupted_examples = JSON.parse(File.read(file_name)).to_set
134
+ @interrupted_examples = JSON.parse(File.read(file_name, encoding: 'UTF-8')).to_set
135
135
  end
136
136
 
137
137
  def load_flaky_examples_cache(cache_dir)
@@ -139,7 +139,7 @@ module RSpecTracer
139
139
 
140
140
  return unless File.file?(file_name)
141
141
 
142
- @flaky_examples = JSON.parse(File.read(file_name)).to_set
142
+ @flaky_examples = JSON.parse(File.read(file_name, encoding: 'UTF-8')).to_set
143
143
  end
144
144
 
145
145
  def load_failed_examples_cache(cache_dir)
@@ -147,7 +147,7 @@ module RSpecTracer
147
147
 
148
148
  return unless File.file?(file_name)
149
149
 
150
- @failed_examples = JSON.parse(File.read(file_name)).to_set
150
+ @failed_examples = JSON.parse(File.read(file_name, encoding: 'UTF-8')).to_set
151
151
  end
152
152
 
153
153
  def load_pending_examples_cache(cache_dir)
@@ -155,7 +155,7 @@ module RSpecTracer
155
155
 
156
156
  return unless File.file?(file_name)
157
157
 
158
- @pending_examples = JSON.parse(File.read(file_name)).to_set
158
+ @pending_examples = JSON.parse(File.read(file_name, encoding: 'UTF-8')).to_set
159
159
  end
160
160
 
161
161
  def load_skipped_examples_cache(cache_dir)
@@ -163,7 +163,7 @@ module RSpecTracer
163
163
 
164
164
  return unless File.file?(file_name)
165
165
 
166
- @skipped_examples = JSON.parse(File.read(file_name)).to_set
166
+ @skipped_examples = JSON.parse(File.read(file_name, encoding: 'UTF-8')).to_set
167
167
  end
168
168
 
169
169
  def load_all_files_cache(cache_dir)
@@ -171,7 +171,7 @@ module RSpecTracer
171
171
 
172
172
  return unless File.file?(file_name)
173
173
 
174
- @all_files = JSON.parse(File.read(file_name)).transform_values do |files|
174
+ @all_files = JSON.parse(File.read(file_name, encoding: 'UTF-8')).transform_values do |files|
175
175
  files.transform_keys(&:to_sym)
176
176
  end
177
177
  end
@@ -181,7 +181,7 @@ module RSpecTracer
181
181
 
182
182
  return unless File.file?(file_name)
183
183
 
184
- @dependency = JSON.parse(File.read(file_name)).transform_values(&:to_set)
184
+ @dependency = JSON.parse(File.read(file_name, encoding: 'UTF-8')).transform_values(&:to_set)
185
185
  end
186
186
 
187
187
  def load_examples_coverage_cache(cache_dir)
@@ -189,7 +189,7 @@ module RSpecTracer
189
189
 
190
190
  return unless File.file?(file_name)
191
191
 
192
- @examples_coverage = JSON.parse(File.read(file_name))
192
+ @examples_coverage = JSON.parse(File.read(file_name, encoding: 'UTF-8'))
193
193
  end
194
194
  end
195
195
  end
@@ -14,7 +14,8 @@ module RSpecTracer
14
14
  reports_dir.each do |report_dir|
15
15
  next unless File.directory?(report_dir)
16
16
 
17
- cache_coverage = JSON.parse(File.read("#{report_dir}/coverage.json"))['RSpecTracer']['coverage']
17
+ raw = File.read("#{report_dir}/coverage.json", encoding: 'UTF-8')
18
+ cache_coverage = JSON.parse(raw)['RSpecTracer']['coverage']
18
19
 
19
20
  cache_coverage.each_pair do |file_name, line_coverage|
20
21
  unless @coverage.key?(file_name)
@@ -165,7 +165,7 @@ module RSpecTracer
165
165
 
166
166
  def jruby_line_stub(file_path)
167
167
  lines = File.foreach(file_path).map { nil }
168
- root_node = ::JRuby.parse(File.read(file_path))
168
+ root_node = ::JRuby.parse(File.read(file_path, encoding: 'UTF-8'))
169
169
 
170
170
  visitor = org.jruby.ast.visitor.NodeVisitor.impl do |_name, node|
171
171
  if node.newline?
@@ -15,7 +15,7 @@ module RSpecTracer
15
15
  }
16
16
  }
17
17
 
18
- File.write(@file_name, JSON.pretty_generate(report))
18
+ File.write(@file_name, JSON.pretty_generate(report), encoding: 'UTF-8')
19
19
  end
20
20
 
21
21
  def print_stats(elapsed_time)
@@ -211,7 +211,7 @@ module RSpecTracer
211
211
  end
212
212
 
213
213
  def template(name)
214
- ERB.new(File.read(File.join(File.dirname(__FILE__), 'views/', "#{name}.erb")))
214
+ ERB.new(File.read(File.join(File.dirname(__FILE__), 'views/', "#{name}.erb"), encoding: 'UTF-8'))
215
215
  end
216
216
 
217
217
  def example_status_css_class(example_status)
@@ -8,6 +8,8 @@ module RSpecTracer
8
8
  def initialize
9
9
  @s3_bucket, @s3_path = setup_s3
10
10
  @aws_cli = RSpecTracer.use_local_aws ? 'awslocal' : 'aws'
11
+ @use_test_suite_id_cache = ENV.fetch('USE_TEST_SUITE_ID_CACHE', nil) == 'true'
12
+ @test_suite_id = ENV.fetch('TEST_SUITE_ID', nil) if @use_test_suite_id_cache
11
13
  end
12
14
 
13
15
  def branch_refs?(branch_name)
@@ -60,7 +62,11 @@ module RSpecTracer
60
62
  end
61
63
 
62
64
  def cache_files_list(ref)
63
- prefix = "s3://#{@s3_bucket}/#{@s3_path}/#{ref}/"
65
+ prefix = if @use_test_suite_id_cache && !@test_suite_id.nil?
66
+ "s3://#{@s3_bucket}/#{@s3_path}/#{ref}/#{@test_suite_id}/"
67
+ else
68
+ "s3://#{@s3_bucket}/#{@s3_path}/#{ref}/"
69
+ end
64
70
 
65
71
  `#{@aws_cli} s3 ls #{prefix} --recursive`.chomp.split("\n")
66
72
  end
@@ -125,7 +131,7 @@ module RSpecTracer
125
131
  remote_dir = s3_dir(ref, run_id)
126
132
  local_dir = File.join(RSpecTracer.cache_path, run_id)
127
133
 
128
- raise AwsError, "Failed to download files from #{local_dir}" unless system(
134
+ raise AwsError, "Failed to upload files from #{local_dir}" unless system(
129
135
  @aws_cli,
130
136
  's3',
131
137
  'cp',
@@ -60,7 +60,7 @@ module RSpecTracer
60
60
 
61
61
  ref_list = @repo.branch_refs.merge(@repo.branch_ref => branch_ref_time.to_i)
62
62
 
63
- File.write(file_name, JSON.pretty_generate(ref_list))
63
+ File.write(file_name, JSON.pretty_generate(ref_list), encoding: 'UTF-8')
64
64
  end
65
65
 
66
66
  def last_run_id
@@ -68,7 +68,7 @@ module RSpecTracer
68
68
 
69
69
  raise CacheError, 'Could not find any local cache to upload' unless File.file?(file_name)
70
70
 
71
- JSON.parse(File.read(file_name))['run_id']
71
+ JSON.parse(File.read(file_name, encoding: 'UTF-8'))['run_id']
72
72
  end
73
73
  end
74
74
  end
@@ -169,7 +169,7 @@ module RSpecTracer
169
169
  file_name = File.join(RSpecTracer.cache_path, 'branch_refs.json')
170
170
 
171
171
  if @aws.download_branch_refs(branch_name, file_name)
172
- @branch_refs = JSON.parse(File.read(file_name)).transform_values(&:to_i)
172
+ @branch_refs = JSON.parse(File.read(file_name, encoding: 'UTF-8')).transform_values(&:to_i)
173
173
 
174
174
  return if @branch_refs.empty?
175
175
 
@@ -3,16 +3,19 @@
3
3
  module RSpecTracer
4
4
  module RemoteCache
5
5
  class Validator
6
+ class ValidationError < StandardError; end
7
+
6
8
  CACHE_FILES_PER_TEST_SUITE = 11
7
9
 
8
10
  def initialize
9
11
  @test_suite_id = ENV.fetch('TEST_SUITE_ID', nil)
10
12
  @test_suites = ENV.fetch('TEST_SUITES', nil)
13
+ @use_test_suite_id_cache = ENV.fetch('USE_TEST_SUITE_ID_CACHE', nil) == 'true'
11
14
 
12
15
  if @test_suite_id.nil? ^ @test_suites.nil?
13
16
  raise(
14
17
  ValidationError,
15
- 'Both the enviornment variables TEST_SUITE_ID and TEST_SUITES are not set'
18
+ 'Both the environment variables TEST_SUITE_ID and TEST_SUITES are not set'
16
19
  )
17
20
  end
18
21
 
@@ -20,13 +23,11 @@ module RSpecTracer
20
23
  end
21
24
 
22
25
  def valid?(ref, cache_files)
23
- last_run_regex = Regexp.new(format(@last_run_files_regex, ref: ref))
24
-
25
- return false if cache_files.count { |file| file.match?(last_run_regex) } != @last_run_files_count
26
-
27
- cache_regex = Regexp.new(format(@cached_files_regex, ref: ref))
28
-
29
- cache_files.count { |file| file.match?(cache_regex) } == @cached_files_count
26
+ if @use_test_suite_id_cache
27
+ test_suite_id_specific_validation?(ref, cache_files)
28
+ else
29
+ general_validation?(ref, cache_files)
30
+ end
30
31
  end
31
32
 
32
33
  private
@@ -36,7 +37,7 @@ module RSpecTracer
36
37
  @last_run_files_count = 1
37
38
  @last_run_files_regex = '/%<ref>s/last_run.json$'
38
39
  @cached_files_count = CACHE_FILES_PER_TEST_SUITE
39
- @cached_files_regex = '/%<ref>s/[0-9a-f]{32}/.+.json'
40
+ @cached_files_regex = '/%<ref>s/[0-9a-f]{32}/.+.json$'
40
41
  else
41
42
  @test_suites = @test_suites.to_i
42
43
  @test_suites_regex = (1..@test_suites).to_a.join('|')
@@ -47,6 +48,25 @@ module RSpecTracer
47
48
  @cached_files_regex = "/%<ref>s/(#{@test_suites_regex})/[0-9a-f]{32}/.+.json$"
48
49
  end
49
50
  end
51
+
52
+ def general_validation?(ref, cache_files)
53
+ last_run_regex = Regexp.new(format(@last_run_files_regex, ref: ref))
54
+
55
+ return false if cache_files.count { |file| file.match?(last_run_regex) } != @last_run_files_count
56
+
57
+ cache_regex = Regexp.new(format(@cached_files_regex, ref: ref))
58
+
59
+ cache_files.count { |file| file.match?(cache_regex) } == @cached_files_count
60
+ end
61
+
62
+ def test_suite_id_specific_validation?(ref, cache_files)
63
+ last_run_regex = Regexp.new("/#{ref}/#{@test_suite_id}/last_run.json$")
64
+ cache_regex = Regexp.new("/#{ref}/#{@test_suite_id}/[0-9a-f]{32}/.+.json$")
65
+
66
+ return false unless cache_files.any? { |file| file.match?(last_run_regex) }
67
+
68
+ cache_files.any? { |file| file.match?(cache_regex) }
69
+ end
50
70
  end
51
71
  end
52
72
  end
@@ -57,7 +57,7 @@ module RSpecTracer
57
57
 
58
58
  def merge_last_run_report(cache_dir)
59
59
  file_name = File.join(cache_dir, 'last_run.json')
60
- cached_last_run = JSON.parse(File.read(file_name), symbolize_names: true)
60
+ cached_last_run = JSON.parse(File.read(file_name, encoding: 'UTF-8'), symbolize_names: true)
61
61
  cached_last_run[:pid] = [cached_last_run[:pid]]
62
62
 
63
63
  cached_last_run.delete_if { |key, _| %i[run_id timestamp].include?(key) }
@@ -68,74 +68,74 @@ module RSpecTracer
68
68
  def write_all_examples_report
69
69
  file_name = File.join(@cache_dir, 'all_examples.json')
70
70
 
71
- File.write(file_name, JSON.pretty_generate(@reporter.all_examples))
71
+ File.write(file_name, JSON.pretty_generate(@reporter.all_examples), encoding: 'UTF-8')
72
72
  end
73
73
 
74
74
  def write_duplicate_examples_report
75
75
  file_name = File.join(@cache_dir, 'duplicate_examples.json')
76
76
 
77
- File.write(file_name, JSON.pretty_generate(@reporter.duplicate_examples))
77
+ File.write(file_name, JSON.pretty_generate(@reporter.duplicate_examples), encoding: 'UTF-8')
78
78
  end
79
79
 
80
80
  def write_interrupted_examples_report
81
81
  file_name = File.join(@cache_dir, 'interrupted_examples.json')
82
82
 
83
- File.write(file_name, JSON.pretty_generate(@reporter.interrupted_examples.sort.to_a))
83
+ File.write(file_name, JSON.pretty_generate(@reporter.interrupted_examples.sort.to_a), encoding: 'UTF-8')
84
84
  end
85
85
 
86
86
  def write_flaky_examples_report
87
87
  file_name = File.join(@cache_dir, 'flaky_examples.json')
88
88
 
89
- File.write(file_name, JSON.pretty_generate(@reporter.flaky_examples.sort.to_a))
89
+ File.write(file_name, JSON.pretty_generate(@reporter.flaky_examples.sort.to_a), encoding: 'UTF-8')
90
90
  end
91
91
 
92
92
  def write_failed_examples_report
93
93
  file_name = File.join(@cache_dir, 'failed_examples.json')
94
94
 
95
- File.write(file_name, JSON.pretty_generate(@reporter.failed_examples.sort.to_a))
95
+ File.write(file_name, JSON.pretty_generate(@reporter.failed_examples.sort.to_a), encoding: 'UTF-8')
96
96
  end
97
97
 
98
98
  def write_pending_examples_report
99
99
  file_name = File.join(@cache_dir, 'pending_examples.json')
100
100
 
101
- File.write(file_name, JSON.pretty_generate(@reporter.pending_examples.sort.to_a))
101
+ File.write(file_name, JSON.pretty_generate(@reporter.pending_examples.sort.to_a), encoding: 'UTF-8')
102
102
  end
103
103
 
104
104
  def write_skipped_examples_report
105
105
  file_name = File.join(@cache_dir, 'skipped_examples.json')
106
106
 
107
- File.write(file_name, JSON.pretty_generate(@reporter.skipped_examples.sort.to_a))
107
+ File.write(file_name, JSON.pretty_generate(@reporter.skipped_examples.sort.to_a), encoding: 'UTF-8')
108
108
  end
109
109
 
110
110
  def write_all_files_report
111
111
  file_name = File.join(@cache_dir, 'all_files.json')
112
112
 
113
- File.write(file_name, JSON.pretty_generate(@reporter.all_files))
113
+ File.write(file_name, JSON.pretty_generate(@reporter.all_files), encoding: 'UTF-8')
114
114
  end
115
115
 
116
116
  def write_dependency_report
117
117
  file_name = File.join(@cache_dir, 'dependency.json')
118
118
 
119
- File.write(file_name, JSON.pretty_generate(@reporter.dependency))
119
+ File.write(file_name, JSON.pretty_generate(@reporter.dependency), encoding: 'UTF-8')
120
120
  end
121
121
 
122
122
  def write_reverse_dependency_report
123
123
  file_name = File.join(@cache_dir, 'reverse_dependency.json')
124
124
 
125
- File.write(file_name, JSON.pretty_generate(@reporter.reverse_dependency))
125
+ File.write(file_name, JSON.pretty_generate(@reporter.reverse_dependency), encoding: 'UTF-8')
126
126
  end
127
127
 
128
128
  def write_examples_coverage_report
129
129
  file_name = File.join(@cache_dir, 'examples_coverage.json')
130
130
 
131
- File.write(file_name, JSON.pretty_generate(@reporter.examples_coverage))
131
+ File.write(file_name, JSON.pretty_generate(@reporter.examples_coverage), encoding: 'UTF-8')
132
132
  end
133
133
 
134
134
  def write_last_run_report
135
135
  file_name = File.join(@report_dir, 'last_run.json')
136
136
  last_run_data = @reporter.last_run.merge(run_id: @run_id, timestamp: Time.now.utc)
137
137
 
138
- File.write(file_name, JSON.pretty_generate(last_run_data))
138
+ File.write(file_name, JSON.pretty_generate(last_run_data), encoding: 'UTF-8')
139
139
  end
140
140
  end
141
141
  end
@@ -12,7 +12,7 @@ module RSpecTracer
12
12
  {
13
13
  file_path: file_path,
14
14
  file_name: file_name(file_path),
15
- digest: Digest::MD5.hexdigest(File.read(file_path))
15
+ digest: Digest::MD5.hexdigest(File.binread(file_path))
16
16
  }
17
17
  end
18
18
 
@@ -25,7 +25,15 @@ module RSpecTracer
25
25
  end
26
26
 
27
27
  def file_path(file_name)
28
+ return file_name if absolute_external_file?(file_name)
29
+
28
30
  File.expand_path(file_name.sub(%r{^/}, ''), RSpecTracer.root)
29
31
  end
32
+
33
+ def absolute_external_file?(file_name)
34
+ file_name.start_with?('/') &&
35
+ !file_name.start_with?(RSpecTracer.root) &&
36
+ File.file?(file_name)
37
+ end
30
38
  end
31
39
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RSpecTracer
4
- VERSION = '1.1.1'
4
+ VERSION = '1.2.0'
5
5
  end
data/lib/rspec_tracer.rb CHANGED
@@ -336,7 +336,7 @@ module RSpecTracer
336
336
 
337
337
  next unless File.directory?(cache_dir)
338
338
 
339
- run_id = JSON.parse(File.read(File.join(cache_dir, 'last_run.json')))['run_id']
339
+ run_id = JSON.parse(File.read(File.join(cache_dir, 'last_run.json'), encoding: 'UTF-8'))['run_id']
340
340
 
341
341
  reports_dir << File.join(cache_dir, run_id)
342
342
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-tracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abhimanyu Singh
@@ -111,7 +111,7 @@ licenses:
111
111
  - MIT
112
112
  metadata:
113
113
  homepage_uri: https://github.com/avmnu-sng/rspec-tracer
114
- source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v1.1.1
114
+ source_code_uri: https://github.com/avmnu-sng/rspec-tracer/tree/v1.2.0
115
115
  changelog_uri: https://github.com/avmnu-sng/rspec-tracer/blob/main/CHANGELOG.md
116
116
  bug_tracker_uri: https://github.com/avmnu-sng/rspec-tracer/issues
117
117
  rdoc_options: []