rspec-tracer 1.2.2 → 2.0.0.pre.1

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 (144) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +197 -45
  3. data/README.md +439 -429
  4. data/bin/rspec-tracer +15 -0
  5. data/lib/rspec_tracer/cache/Rakefile +43 -0
  6. data/lib/rspec_tracer/cli/cache_clear.rb +98 -0
  7. data/lib/rspec_tracer/cli/cache_info.rb +103 -0
  8. data/lib/rspec_tracer/cli/doctor.rb +275 -0
  9. data/lib/rspec_tracer/cli/explain.rb +148 -0
  10. data/lib/rspec_tracer/cli/report_open.rb +82 -0
  11. data/lib/rspec_tracer/cli.rb +116 -0
  12. data/lib/rspec_tracer/configuration.rb +1100 -3
  13. data/lib/rspec_tracer/engine.rb +1076 -0
  14. data/lib/rspec_tracer/example.rb +21 -6
  15. data/lib/rspec_tracer/filter.rb +35 -0
  16. data/lib/rspec_tracer/line_stub.rb +61 -0
  17. data/lib/rspec_tracer/load_config.rb +2 -2
  18. data/lib/rspec_tracer/logger.rb +15 -0
  19. data/lib/rspec_tracer/rails/README.md +78 -0
  20. data/lib/rspec_tracer/rails/i18n_tracking.rb +137 -0
  21. data/lib/rspec_tracer/rails/notifications.rb +263 -0
  22. data/lib/rspec_tracer/rails/preset.rb +94 -0
  23. data/lib/rspec_tracer/rails/railtie.rb +22 -0
  24. data/lib/rspec_tracer/rails.rb +15 -0
  25. data/lib/rspec_tracer/remote_cache/README.md +140 -0
  26. data/lib/rspec_tracer/remote_cache/Rakefile +35 -11
  27. data/lib/rspec_tracer/remote_cache/archive.rb +137 -0
  28. data/lib/rspec_tracer/remote_cache/backend.rb +73 -0
  29. data/lib/rspec_tracer/remote_cache/git_ancestry.rb +241 -0
  30. data/lib/rspec_tracer/remote_cache/local_fs_backend.rb +439 -0
  31. data/lib/rspec_tracer/remote_cache/redis_backend.rb +554 -0
  32. data/lib/rspec_tracer/remote_cache/s3_backend.rb +712 -0
  33. data/lib/rspec_tracer/remote_cache/user_tasks.rb +397 -0
  34. data/lib/rspec_tracer/remote_cache/validator.rb +40 -62
  35. data/lib/rspec_tracer/remote_cache.rb +22 -0
  36. data/lib/rspec_tracer/reporters/README.md +103 -0
  37. data/lib/rspec_tracer/reporters/base.rb +87 -0
  38. data/lib/rspec_tracer/reporters/coverage_json_reporter.rb +338 -0
  39. data/lib/rspec_tracer/reporters/html/.gitignore +19 -0
  40. data/lib/rspec_tracer/reporters/html/.prettierignore +4 -0
  41. data/lib/rspec_tracer/reporters/html/.prettierrc.json +9 -0
  42. data/lib/rspec_tracer/reporters/html/README.md +80 -0
  43. data/lib/rspec_tracer/reporters/html/dist/assets/index.css +2 -0
  44. data/lib/rspec_tracer/reporters/html/dist/assets/index.js +1 -0
  45. data/lib/rspec_tracer/reporters/html/dist/index.html +24 -0
  46. data/lib/rspec_tracer/reporters/html/eslint.config.js +62 -0
  47. data/lib/rspec_tracer/reporters/html/package-lock.json +4941 -0
  48. data/lib/rspec_tracer/reporters/html/package.json +29 -0
  49. data/lib/rspec_tracer/reporters/html/src/app.jsx +130 -0
  50. data/lib/rspec_tracer/reporters/html/src/components/AllExamples.jsx +86 -0
  51. data/lib/rspec_tracer/reporters/html/src/components/DuplicateExamples.jsx +68 -0
  52. data/lib/rspec_tracer/reporters/html/src/components/ExamplesDependency.jsx +78 -0
  53. data/lib/rspec_tracer/reporters/html/src/components/FilesDependency.jsx +72 -0
  54. data/lib/rspec_tracer/reporters/html/src/components/FlakyExamples.jsx +42 -0
  55. data/lib/rspec_tracer/reporters/html/src/components/ReportTable.jsx +131 -0
  56. data/lib/rspec_tracer/reporters/html/src/components/SearchBar.jsx +19 -0
  57. data/lib/rspec_tracer/reporters/html/src/index.html +23 -0
  58. data/lib/rspec_tracer/reporters/html/src/main.jsx +37 -0
  59. data/lib/rspec_tracer/reporters/html/src/styles.css +434 -0
  60. data/lib/rspec_tracer/reporters/html/vite.config.js +42 -0
  61. data/lib/rspec_tracer/reporters/html_reporter.rb +266 -0
  62. data/lib/rspec_tracer/reporters/json_reporter.rb +88 -0
  63. data/lib/rspec_tracer/reporters/payload_builder.rb +235 -0
  64. data/lib/rspec_tracer/reporters/registry.rb +120 -0
  65. data/lib/rspec_tracer/reporters/terminal_reporter.rb +264 -0
  66. data/lib/rspec_tracer/rspec/README.md +73 -0
  67. data/lib/rspec_tracer/rspec/installation.rb +97 -0
  68. data/lib/rspec_tracer/rspec/metadata.rb +96 -0
  69. data/lib/rspec_tracer/rspec/parallel_tests.rb +459 -0
  70. data/lib/rspec_tracer/rspec/reporter_hook.rb +84 -0
  71. data/lib/rspec_tracer/rspec/runner_hook.rb +178 -0
  72. data/lib/rspec_tracer/source_file.rb +24 -7
  73. data/lib/rspec_tracer/storage/README.md +35 -0
  74. data/lib/rspec_tracer/storage/backend.rb +68 -0
  75. data/lib/rspec_tracer/storage/json_backend.rb +866 -0
  76. data/lib/rspec_tracer/storage/lazy_snapshot.rb +65 -0
  77. data/lib/rspec_tracer/storage/schema.rb +43 -0
  78. data/lib/rspec_tracer/storage/serializer/json.rb +41 -0
  79. data/lib/rspec_tracer/storage/serializer/msgpack.rb +90 -0
  80. data/lib/rspec_tracer/storage/snapshot.rb +127 -0
  81. data/lib/rspec_tracer/storage/sqlite_backend.rb +686 -0
  82. data/lib/rspec_tracer/time_formatter.rb +37 -18
  83. data/lib/rspec_tracer/tracker/README.md +36 -0
  84. data/lib/rspec_tracer/tracker/coverage_adapter.rb +174 -0
  85. data/lib/rspec_tracer/tracker/declared_globs.rb +100 -0
  86. data/lib/rspec_tracer/tracker/dependency_graph.rb +134 -0
  87. data/lib/rspec_tracer/tracker/env_matcher.rb +127 -0
  88. data/lib/rspec_tracer/tracker/env_snapshot.rb +77 -0
  89. data/lib/rspec_tracer/tracker/example_registry.rb +153 -0
  90. data/lib/rspec_tracer/tracker/file_digest.rb +61 -0
  91. data/lib/rspec_tracer/tracker/filter.rb +127 -0
  92. data/lib/rspec_tracer/tracker/input.rb +99 -0
  93. data/lib/rspec_tracer/tracker/io_hooks/file.rb +55 -0
  94. data/lib/rspec_tracer/tracker/io_hooks/io.rb +24 -0
  95. data/lib/rspec_tracer/tracker/io_hooks/json.rb +23 -0
  96. data/lib/rspec_tracer/tracker/io_hooks/kernel.rb +26 -0
  97. data/lib/rspec_tracer/tracker/io_hooks/yaml.rb +38 -0
  98. data/lib/rspec_tracer/tracker/io_hooks.rb +195 -0
  99. data/lib/rspec_tracer/tracker/loaded_files_tracker.rb +295 -0
  100. data/lib/rspec_tracer/tracker/new_file_detector.rb +62 -0
  101. data/lib/rspec_tracer/tracker/whole_suite_invalidators.rb +96 -0
  102. data/lib/rspec_tracer/version.rb +4 -1
  103. data/lib/rspec_tracer.rb +232 -381
  104. metadata +93 -43
  105. data/lib/rspec_tracer/cache.rb +0 -207
  106. data/lib/rspec_tracer/coverage_merger.rb +0 -42
  107. data/lib/rspec_tracer/coverage_reporter.rb +0 -187
  108. data/lib/rspec_tracer/coverage_writer.rb +0 -58
  109. data/lib/rspec_tracer/html_reporter/Rakefile +0 -18
  110. data/lib/rspec_tracer/html_reporter/assets/javascripts/application.js +0 -56
  111. data/lib/rspec_tracer/html_reporter/assets/javascripts/libraries/jquery.js +0 -10881
  112. data/lib/rspec_tracer/html_reporter/assets/javascripts/plugins/datatables.js +0 -15381
  113. data/lib/rspec_tracer/html_reporter/assets/stylesheets/application.css +0 -196
  114. data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/datatables.css +0 -459
  115. data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/jquery-ui.css +0 -436
  116. data/lib/rspec_tracer/html_reporter/assets/stylesheets/print.css +0 -92
  117. data/lib/rspec_tracer/html_reporter/assets/stylesheets/reset.css +0 -265
  118. data/lib/rspec_tracer/html_reporter/public/application.css +0 -5
  119. data/lib/rspec_tracer/html_reporter/public/application.js +0 -6
  120. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc.png +0 -0
  121. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc_disabled.png +0 -0
  122. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_both.png +0 -0
  123. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc.png +0 -0
  124. data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc_disabled.png +0 -0
  125. data/lib/rspec_tracer/html_reporter/public/favicon.png +0 -0
  126. data/lib/rspec_tracer/html_reporter/public/loading.gif +0 -0
  127. data/lib/rspec_tracer/html_reporter/reporter.rb +0 -242
  128. data/lib/rspec_tracer/html_reporter/views/duplicate_examples.erb +0 -34
  129. data/lib/rspec_tracer/html_reporter/views/examples.erb +0 -58
  130. data/lib/rspec_tracer/html_reporter/views/examples_dependency.erb +0 -36
  131. data/lib/rspec_tracer/html_reporter/views/files_dependency.erb +0 -36
  132. data/lib/rspec_tracer/html_reporter/views/flaky_examples.erb +0 -38
  133. data/lib/rspec_tracer/html_reporter/views/layout.erb +0 -38
  134. data/lib/rspec_tracer/remote_cache/aws.rb +0 -176
  135. data/lib/rspec_tracer/remote_cache/cache.rb +0 -75
  136. data/lib/rspec_tracer/remote_cache/repo.rb +0 -210
  137. data/lib/rspec_tracer/report_generator.rb +0 -158
  138. data/lib/rspec_tracer/report_merger.rb +0 -68
  139. data/lib/rspec_tracer/report_writer.rb +0 -141
  140. data/lib/rspec_tracer/reporter.rb +0 -204
  141. data/lib/rspec_tracer/rspec_reporter.rb +0 -41
  142. data/lib/rspec_tracer/rspec_runner.rb +0 -56
  143. data/lib/rspec_tracer/ruby_coverage.rb +0 -9
  144. data/lib/rspec_tracer/runner.rb +0 -278
@@ -1,75 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'aws'
4
- require_relative 'repo'
5
- require_relative 'validator'
6
-
7
- module RSpecTracer
8
- module RemoteCache
9
- class Cache
10
- class CacheError < StandardError; end
11
-
12
- def initialize
13
- @aws = RSpecTracer::RemoteCache::Aws.new
14
- @repo = RSpecTracer::RemoteCache::Repo.new(@aws)
15
- end
16
-
17
- def download
18
- return unless cache_ref?
19
-
20
- @aws.download_file(@cache_sha, 'last_run.json')
21
- @aws.download_dir(@cache_sha, last_run_id)
22
- end
23
-
24
- def upload
25
- @aws.upload_file(@repo.branch_ref, 'last_run.json')
26
- @aws.upload_dir(@repo.branch_ref, last_run_id)
27
-
28
- file_name = File.join(RSpecTracer.cache_path, 'branch_refs.json')
29
-
30
- write_branch_refs(file_name)
31
- @aws.upload_branch_refs(@repo.branch_name, file_name)
32
- end
33
-
34
- private
35
-
36
- def cache_ref?
37
- cache_validator = RSpecTracer::RemoteCache::Validator.new
38
-
39
- @cache_sha = @repo.cache_refs.each_key.detect do |ref|
40
- RSpecTracer.logger.debug "Validating ref #{ref}"
41
-
42
- cache_validator.valid?(ref, @aws.cache_files_list(ref))
43
- end
44
-
45
- if @cache_sha.nil?
46
- RSpecTracer.logger.warn 'Could not find a suitable cache sha to download'
47
-
48
- return false
49
- end
50
-
51
- true
52
- end
53
-
54
- def write_branch_refs(file_name)
55
- branch_ref_time = `git show --no-patch --format="%ct" #{@repo.branch_ref}`.chomp
56
-
57
- unless $CHILD_STATUS.success?
58
- RSpecTracer.logger.warn "Failed to find object #{@repo.branch_ref} commit timestamp"
59
- end
60
-
61
- ref_list = @repo.branch_refs.merge(@repo.branch_ref => branch_ref_time.to_i)
62
-
63
- File.write(file_name, JSON.pretty_generate(ref_list), encoding: 'UTF-8')
64
- end
65
-
66
- def last_run_id
67
- file_name = File.join(RSpecTracer.cache_path, 'last_run.json')
68
-
69
- raise CacheError, 'Could not find any local cache to upload' unless File.file?(file_name)
70
-
71
- JSON.parse(File.read(file_name, encoding: 'UTF-8'))['run_id']
72
- end
73
- end
74
- end
75
- end
@@ -1,210 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecTracer
4
- module RemoteCache
5
- class Repo
6
- class RepoError < StandardError; end
7
-
8
- attr_reader :default_branch_name, :branch_name, :branch_ref, :branch_refs, :ancestry_refs, :cache_refs
9
-
10
- def initialize(aws)
11
- raise RepoError, 'GIT_DEFAULT_BRANCH environment variable is not set' if ENV['GIT_DEFAULT_BRANCH'].nil?
12
- raise RepoError, 'GIT_BRANCH environment variable is not set' if ENV['GIT_BRANCH'].nil?
13
-
14
- @aws = aws
15
- @default_branch_name = ENV['GIT_DEFAULT_BRANCH'].chomp
16
- @branch_name = ENV['GIT_BRANCH'].chomp
17
-
18
- merge_base_branch
19
- fetch_head_ref
20
- fetch_branch_ref
21
- fetch_ancestry_refs
22
- fetch_branch_refs
23
- generate_cache_refs
24
- end
25
-
26
- private
27
-
28
- def merge_base_branch
29
- return if @default_branch_name == @branch_name
30
-
31
- pull_remote_branch if current_branch != @branch_name
32
-
33
- merge_default_branch
34
- end
35
-
36
- def current_branch
37
- branch = `git rev-parse --abbrev-ref HEAD`.chomp
38
-
39
- return branch if $CHILD_STATUS.success?
40
-
41
- raise RepoError, 'Could not determine current branch'
42
- end
43
-
44
- def pull_remote_branch
45
- return if system(
46
- 'git',
47
- 'fetch',
48
- 'origin',
49
- "#{@branch_name}:#{@branch_name}",
50
- out: File::NULL,
51
- err: File::NULL
52
- ) && system(
53
- 'git',
54
- 'checkout',
55
- @branch_name,
56
- out: File::NULL,
57
- err: File::NULL
58
- )
59
-
60
- raise RepoError, "Could not pull remote branch #{@branch_name}"
61
- end
62
-
63
- def merge_default_branch
64
- return if system(
65
- 'git',
66
- 'merge',
67
- "origin/#{@default_branch_name}",
68
- '--no-edit',
69
- '--no-ff',
70
- out: File::NULL,
71
- err: File::NULL
72
- )
73
-
74
- raise RepoError, "Could not merge #{@default_branch_name} into #{@branch_name}"
75
- end
76
-
77
- def fetch_head_ref
78
- @head_ref = `git rev-parse HEAD`.chomp
79
-
80
- raise RepoError, 'Could not find HEAD commit sha' unless $CHILD_STATUS.success?
81
- end
82
-
83
- def fetch_branch_ref
84
- if merged?
85
- fetch_merged_parents
86
-
87
- @branch_ref = @merged_parents.first
88
- @ignored_refs = [@head_ref]
89
- else
90
- @branch_ref = @head_ref
91
- @ignored_refs = []
92
- end
93
- end
94
-
95
- def merged?
96
- system('git', 'rev-parse', 'HEAD^2', out: File::NULL, err: File::NULL)
97
- end
98
-
99
- def fetch_merged_parents
100
- @merged_parents = []
101
-
102
- first_parent = `git rev-parse HEAD^1`.chomp
103
- @merged_parents << first_parent if $CHILD_STATUS.success?
104
-
105
- second_parent = `git rev-parse HEAD^2`.chomp
106
- @merged_parents << second_parent if $CHILD_STATUS.success?
107
-
108
- raise RepoError, 'Could not find merge commit parents' if @merged_parents.length != 2
109
- end
110
-
111
- def fetch_ancestry_refs
112
- ref_list = Set.new
113
- ref_list |= `git rev-list --max-count=25 #{@branch_ref}..origin/HEAD`.chomp.split if merged?
114
- ref_list |= `git rev-list --max-count=25 #{@branch_ref}`.chomp.split
115
-
116
- raise RepoError, 'Could not find ancestry refs' unless $CHILD_STATUS.success?
117
-
118
- @ancestry_refs = refs_committer_timestamp(ref_list - @ignored_refs)
119
-
120
- return if @ancestry_refs.empty?
121
-
122
- print_refs(@ancestry_refs, 'ancestry')
123
- end
124
-
125
- def fetch_branch_refs
126
- unless @aws.branch_refs?(@branch_name)
127
- RSpecTracer.logger.warn "No branch refs for #{@branch_name} branch found in S3"
128
-
129
- @branch_refs = {}
130
-
131
- return
132
- end
133
-
134
- download_branch_refs
135
- end
136
-
137
- def generate_cache_refs
138
- ref_list = @ancestry_refs.merge(@branch_refs)
139
-
140
- if ref_list.empty?
141
- @cache_refs = {}
142
-
143
- return
144
- end
145
-
146
- @cache_refs = ref_list.sort_by { |_, timestamp| -timestamp }.to_h
147
-
148
- print_refs(@cache_refs, 'cache')
149
- end
150
-
151
- def refs_committer_timestamp(ref_list)
152
- return {} if ref_list.empty?
153
-
154
- command = <<-COMMAND.strip.gsub(/\s+/, ' ')
155
- git show
156
- --no-patch
157
- --format="%H %ct"
158
- #{ref_list.to_a.join(' ')}
159
- COMMAND
160
-
161
- ref_timestamp = `#{command}`.chomp
162
-
163
- raise RepoError, 'Could not find ancestry refs' unless $CHILD_STATUS.success?
164
-
165
- ref_timestamp.split("\n").to_h(&:split).transform_values(&:to_i)
166
- end
167
-
168
- def download_branch_refs
169
- file_name = File.join(RSpecTracer.cache_path, 'branch_refs.json')
170
-
171
- if @aws.download_branch_refs(branch_name, file_name)
172
- @branch_refs = JSON.parse(File.read(file_name, encoding: 'UTF-8')).transform_values(&:to_i)
173
-
174
- return if @branch_refs.empty?
175
-
176
- filter_branch_refs
177
- print_refs(@branch_refs, 'branch')
178
- else
179
- @branch_refs = {}
180
-
181
- FileUtils.rm_f(file_name)
182
-
183
- RSpecTracer.logger.error "Failed to fetch branch refs for #{@branch_name} branch"
184
- end
185
- end
186
-
187
- def filter_branch_refs
188
- if @ancestry_refs.empty?
189
- @branch_refs = @branch_refs.sort_by { |_, timestamp| -timestamp }.first(25).to_h
190
-
191
- return
192
- end
193
-
194
- oldest_ancestry_time = @ancestry_refs.values.min
195
-
196
- @branch_refs = @branch_refs
197
- .select { |_, timestamp| timestamp >= oldest_ancestry_time }
198
- .sort_by { |_, timestamp| -timestamp }
199
- .first(25)
200
- .to_h
201
- end
202
-
203
- def print_refs(refs, type)
204
- refs_list = refs.map { |ref, timestamp| " * #{ref} (commit timestamp: #{timestamp})" }.join("\n")
205
-
206
- RSpecTracer.logger.debug "Fetched the following #{type} refs for #{@branch_name} branch:\n#{refs_list}"
207
- end
208
- end
209
- end
210
- end
@@ -1,158 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecTracer
4
- class ReportGenerator
5
- def initialize(reporter, cache)
6
- @reporter = reporter
7
- @cache = cache
8
- end
9
-
10
- def reverse_dependency_report
11
- reverse_dependency = Hash.new do |examples, file_name|
12
- examples[file_name] = {
13
- example_count: 0,
14
- examples: Hash.new(0)
15
- }
16
- end
17
-
18
- @reporter.dependency.each_pair do |example_id, files|
19
- next if @reporter.interrupted_examples.include?(example_id)
20
-
21
- example_file = @reporter.all_examples[example_id][:rerun_file_name]
22
-
23
- files.each do |file_name|
24
- reverse_dependency[file_name][:example_count] += 1
25
- reverse_dependency[file_name][:examples][example_file] += 1
26
- end
27
- end
28
-
29
- reverse_dependency.transform_values! do |data|
30
- {
31
- example_count: data[:example_count],
32
- examples: data[:examples].sort_by { |file_name, count| [-count, file_name] }.to_h
33
- }
34
- end
35
-
36
- reverse_dependency.sort_by { |file_name, data| [-data[:example_count], file_name] }.to_h
37
- end
38
-
39
- def generate_report
40
- generate_last_run_report
41
- generate_examples_status_report
42
-
43
- %i[all_files all_examples dependency examples_coverage reverse_dependency].each do |report_type|
44
- starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
45
-
46
- send("generate_#{report_type}_report")
47
-
48
- ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
49
- elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
50
-
51
- RSpecTracer.logger.debug "RSpec tracer generated #{report_type.to_s.tr('_', ' ')} report (took #{elapsed})"
52
- end
53
- end
54
-
55
- private
56
-
57
- def generate_last_run_report
58
- @reporter.last_run = {
59
- pid: RSpecTracer.pid,
60
- actual_count: RSpec.world.example_count + @reporter.skipped_examples.count,
61
- example_count: RSpec.world.example_count,
62
- duplicate_examples: @reporter.duplicate_examples.sum { |_, examples| examples.count },
63
- interrupted_examples: @reporter.interrupted_examples.count,
64
- failed_examples: @reporter.failed_examples.count,
65
- skipped_examples: @reporter.skipped_examples.count,
66
- pending_examples: @reporter.pending_examples.count,
67
- flaky_examples: @reporter.flaky_examples.count
68
- }
69
- end
70
-
71
- def generate_examples_status_report
72
- starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
73
-
74
- generate_flaky_examples_report
75
- generate_failed_examples_report
76
- generate_pending_examples_report
77
-
78
- ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
79
- elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
80
-
81
- RSpecTracer.logger.debug "RSpec tracer generated flaky, failed, and pending examples report (took #{elapsed})"
82
- end
83
-
84
- def generate_flaky_examples_report
85
- @reporter.possibly_flaky_examples.each do |example_id|
86
- next if @reporter.example_deleted?(example_id)
87
- next unless @cache.flaky_examples.include?(example_id) ||
88
- @reporter.example_passed?(example_id)
89
-
90
- @reporter.register_flaky_example(example_id)
91
- end
92
- end
93
-
94
- def generate_failed_examples_report
95
- @cache.failed_examples.each do |example_id|
96
- next if @reporter.example_deleted?(example_id) ||
97
- @reporter.all_examples.key?(example_id)
98
-
99
- @reporter.register_failed_example(example_id)
100
- end
101
- end
102
-
103
- def generate_pending_examples_report
104
- @cache.pending_examples.each do |example_id|
105
- next if @reporter.example_deleted?(example_id) ||
106
- @reporter.all_examples.key?(example_id)
107
-
108
- @reporter.register_pending_example(example_id)
109
- end
110
- end
111
-
112
- def generate_all_files_report
113
- @cache.all_files.each_pair do |file_name, data|
114
- next if @reporter.all_files.key?(file_name) ||
115
- @reporter.file_deleted?(file_name)
116
-
117
- @reporter.all_files[file_name] = data
118
- end
119
- end
120
-
121
- def generate_all_examples_report
122
- @cache.all_examples.each_pair do |example_id, data|
123
- next if @reporter.all_examples.key?(example_id) ||
124
- @reporter.example_deleted?(example_id)
125
-
126
- @reporter.all_examples[example_id] = data
127
- end
128
- end
129
-
130
- def generate_dependency_report
131
- @cache.dependency.each_pair do |example_id, data|
132
- next if @reporter.dependency.key?(example_id) ||
133
- @reporter.example_deleted?(example_id)
134
-
135
- @reporter.dependency[example_id] = data.reject do |file_name|
136
- @reporter.file_deleted?(file_name)
137
- end
138
- end
139
-
140
- @reporter.dependency.transform_values!(&:to_a)
141
- end
142
-
143
- def generate_examples_coverage_report
144
- @cache.cached_examples_coverage.each_pair do |example_id, data|
145
- next if @reporter.examples_coverage.key?(example_id) ||
146
- @reporter.example_deleted?(example_id)
147
-
148
- @reporter.examples_coverage[example_id] = data.reject do |file_name|
149
- @reporter.file_deleted?(file_name)
150
- end
151
- end
152
- end
153
-
154
- def generate_reverse_dependency_report
155
- @reporter.reverse_dependency = reverse_dependency_report
156
- end
157
- end
158
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecTracer
4
- class ReportMerger
5
- attr_reader :all_examples, :duplicate_examples, :interrupted_examples,
6
- :flaky_examples, :failed_examples, :pending_examples, :skipped_examples,
7
- :all_files, :dependency, :reverse_dependency, :examples_coverage, :last_run
8
-
9
- def initialize
10
- @last_run = {}
11
- @all_examples = {}
12
- @duplicate_examples = {}
13
- @interrupted_examples = Set.new
14
- @flaky_examples = Set.new
15
- @failed_examples = Set.new
16
- @pending_examples = Set.new
17
- @skipped_examples = Set.new
18
- @all_files = {}
19
- @dependency = Hash.new { |hash, key| hash[key] = Set.new }
20
- @reverse_dependency = {}
21
- @examples_coverage = {}
22
- end
23
-
24
- def merge(reports_dir)
25
- reports_dir.each do |report_dir|
26
- next unless File.directory?(report_dir)
27
-
28
- merge_cache(load_cache(report_dir))
29
- merge_last_run_report(File.dirname(report_dir))
30
- end
31
-
32
- @dependency.transform_values!(&:to_a)
33
-
34
- @reverse_dependency = RSpecTracer::ReportGenerator.new(self, nil).reverse_dependency_report
35
- end
36
-
37
- private
38
-
39
- def load_cache(cache_dir)
40
- RSpecTracer::Cache.new.populate_from_disk(cache_dir, discard_run_reason: false)
41
- end
42
-
43
- def merge_cache(cache)
44
- @all_examples.merge!(cache.all_examples) { |_, v1, v2| v1[:run_reason] ? v1 : v2 }
45
- @duplicate_examples.merge!(cache.duplicate_examples) { |_, v1, v2| v1 + v2 }
46
- @interrupted_examples.merge(cache.interrupted_examples)
47
- @flaky_examples.merge(cache.flaky_examples)
48
- @failed_examples.merge(cache.failed_examples)
49
- @pending_examples.merge(cache.pending_examples)
50
- @skipped_examples.merge(cache.skipped_examples)
51
- @all_files.merge!(cache.all_files)
52
- @dependency.merge!(cache.dependency) { |_, v1, v2| v1.merge(v2) }
53
- @examples_coverage.merge!(cache.examples_coverage) do |_, v1, v2|
54
- v1.merge(v2) { |_, v3, v4| v3.merge(v4) { |_, v5, v6| v5 + v6 } }
55
- end
56
- end
57
-
58
- def merge_last_run_report(cache_dir)
59
- file_name = File.join(cache_dir, 'last_run.json')
60
- cached_last_run = JSON.parse(File.read(file_name, encoding: 'UTF-8'), symbolize_names: true)
61
- cached_last_run[:pid] = [cached_last_run[:pid]]
62
-
63
- cached_last_run.delete_if { |key, _| %i[run_id timestamp].include?(key) }
64
-
65
- @last_run.merge!(cached_last_run) { |_, v1, v2| v1 + v2 }
66
- end
67
- end
68
- end
@@ -1,141 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpecTracer
4
- class ReportWriter
5
- def initialize(report_dir, reporter)
6
- @report_dir = report_dir
7
- @reporter = reporter
8
- end
9
-
10
- def write_report
11
- starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
12
-
13
- @run_id = Digest::MD5.hexdigest(@reporter.all_examples.keys.sort.to_json)
14
- @cache_dir = File.join(@report_dir, @run_id)
15
-
16
- FileUtils.mkdir_p(@cache_dir)
17
-
18
- write_all_examples_report
19
- write_duplicate_examples_report
20
- write_interrupted_examples_report
21
- write_flaky_examples_report
22
- write_failed_examples_report
23
- write_pending_examples_report
24
- write_skipped_examples_report
25
- write_all_files_report
26
- write_dependency_report
27
- write_reverse_dependency_report
28
- write_examples_coverage_report
29
- write_last_run_report
30
-
31
- ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
32
- elapsed = RSpecTracer::TimeFormatter.format_time(ending - starting)
33
-
34
- RSpecTracer.logger.debug "RSpec tracer reports written to #{@cache_dir} (took #{elapsed})"
35
- end
36
-
37
- def print_duplicate_examples
38
- return if @reporter.duplicate_examples.empty?
39
-
40
- total = @reporter.duplicate_examples.sum { |_, examples| examples.count }
41
-
42
- RSpecTracer.logger.error [
43
- '=' * 80,
44
- ' IMPORTANT NOTICE -- RSPEC TRACER COULD NOT IDENTIFY SOME EXAMPLES UNIQUELY',
45
- '=' * 80,
46
- "RSpec tracer could not uniquely identify the following #{total} examples:"
47
- ].join("\n")
48
-
49
- justify = ' ' * 2
50
- nested_justify = justify * 3
51
-
52
- @reporter.duplicate_examples.each_pair do |example_id, examples|
53
- RSpecTracer.logger.error "#{justify}- Example ID: #{example_id} (#{examples.count} examples)"
54
-
55
- examples.each do |example|
56
- description = example[:full_description].strip
57
- file_name = example[:rerun_file_name].sub(%r{^/}, '')
58
- line_number = example[:rerun_line_number]
59
- location = "#{file_name}:#{line_number}"
60
-
61
- RSpecTracer.logger.error "#{nested_justify}* #{description} (#{location})"
62
- end
63
- end
64
- end
65
-
66
- private
67
-
68
- def write_all_examples_report
69
- file_name = File.join(@cache_dir, 'all_examples.json')
70
-
71
- File.write(file_name, JSON.pretty_generate(@reporter.all_examples), encoding: 'UTF-8')
72
- end
73
-
74
- def write_duplicate_examples_report
75
- file_name = File.join(@cache_dir, 'duplicate_examples.json')
76
-
77
- File.write(file_name, JSON.pretty_generate(@reporter.duplicate_examples), encoding: 'UTF-8')
78
- end
79
-
80
- def write_interrupted_examples_report
81
- file_name = File.join(@cache_dir, 'interrupted_examples.json')
82
-
83
- File.write(file_name, JSON.pretty_generate(@reporter.interrupted_examples.sort.to_a), encoding: 'UTF-8')
84
- end
85
-
86
- def write_flaky_examples_report
87
- file_name = File.join(@cache_dir, 'flaky_examples.json')
88
-
89
- File.write(file_name, JSON.pretty_generate(@reporter.flaky_examples.sort.to_a), encoding: 'UTF-8')
90
- end
91
-
92
- def write_failed_examples_report
93
- file_name = File.join(@cache_dir, 'failed_examples.json')
94
-
95
- File.write(file_name, JSON.pretty_generate(@reporter.failed_examples.sort.to_a), encoding: 'UTF-8')
96
- end
97
-
98
- def write_pending_examples_report
99
- file_name = File.join(@cache_dir, 'pending_examples.json')
100
-
101
- File.write(file_name, JSON.pretty_generate(@reporter.pending_examples.sort.to_a), encoding: 'UTF-8')
102
- end
103
-
104
- def write_skipped_examples_report
105
- file_name = File.join(@cache_dir, 'skipped_examples.json')
106
-
107
- File.write(file_name, JSON.pretty_generate(@reporter.skipped_examples.sort.to_a), encoding: 'UTF-8')
108
- end
109
-
110
- def write_all_files_report
111
- file_name = File.join(@cache_dir, 'all_files.json')
112
-
113
- File.write(file_name, JSON.pretty_generate(@reporter.all_files), encoding: 'UTF-8')
114
- end
115
-
116
- def write_dependency_report
117
- file_name = File.join(@cache_dir, 'dependency.json')
118
-
119
- File.write(file_name, JSON.pretty_generate(@reporter.dependency), encoding: 'UTF-8')
120
- end
121
-
122
- def write_reverse_dependency_report
123
- file_name = File.join(@cache_dir, 'reverse_dependency.json')
124
-
125
- File.write(file_name, JSON.pretty_generate(@reporter.reverse_dependency), encoding: 'UTF-8')
126
- end
127
-
128
- def write_examples_coverage_report
129
- file_name = File.join(@cache_dir, 'examples_coverage.json')
130
-
131
- File.write(file_name, JSON.pretty_generate(@reporter.examples_coverage), encoding: 'UTF-8')
132
- end
133
-
134
- def write_last_run_report
135
- file_name = File.join(@report_dir, 'last_run.json')
136
- last_run_data = @reporter.last_run.merge(run_id: @run_id, timestamp: Time.now.utc)
137
-
138
- File.write(file_name, JSON.pretty_generate(last_run_data), encoding: 'UTF-8')
139
- end
140
- end
141
- end