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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +197 -45
- data/README.md +439 -429
- data/bin/rspec-tracer +15 -0
- data/lib/rspec_tracer/cache/Rakefile +43 -0
- data/lib/rspec_tracer/cli/cache_clear.rb +98 -0
- data/lib/rspec_tracer/cli/cache_info.rb +103 -0
- data/lib/rspec_tracer/cli/doctor.rb +275 -0
- data/lib/rspec_tracer/cli/explain.rb +148 -0
- data/lib/rspec_tracer/cli/report_open.rb +82 -0
- data/lib/rspec_tracer/cli.rb +116 -0
- data/lib/rspec_tracer/configuration.rb +1100 -3
- data/lib/rspec_tracer/engine.rb +1076 -0
- data/lib/rspec_tracer/example.rb +21 -6
- data/lib/rspec_tracer/filter.rb +35 -0
- data/lib/rspec_tracer/line_stub.rb +61 -0
- data/lib/rspec_tracer/load_config.rb +2 -2
- data/lib/rspec_tracer/logger.rb +15 -0
- data/lib/rspec_tracer/rails/README.md +78 -0
- data/lib/rspec_tracer/rails/i18n_tracking.rb +137 -0
- data/lib/rspec_tracer/rails/notifications.rb +263 -0
- data/lib/rspec_tracer/rails/preset.rb +94 -0
- data/lib/rspec_tracer/rails/railtie.rb +22 -0
- data/lib/rspec_tracer/rails.rb +15 -0
- data/lib/rspec_tracer/remote_cache/README.md +140 -0
- data/lib/rspec_tracer/remote_cache/Rakefile +35 -11
- data/lib/rspec_tracer/remote_cache/archive.rb +137 -0
- data/lib/rspec_tracer/remote_cache/backend.rb +73 -0
- data/lib/rspec_tracer/remote_cache/git_ancestry.rb +241 -0
- data/lib/rspec_tracer/remote_cache/local_fs_backend.rb +439 -0
- data/lib/rspec_tracer/remote_cache/redis_backend.rb +554 -0
- data/lib/rspec_tracer/remote_cache/s3_backend.rb +712 -0
- data/lib/rspec_tracer/remote_cache/user_tasks.rb +397 -0
- data/lib/rspec_tracer/remote_cache/validator.rb +40 -62
- data/lib/rspec_tracer/remote_cache.rb +22 -0
- data/lib/rspec_tracer/reporters/README.md +103 -0
- data/lib/rspec_tracer/reporters/base.rb +87 -0
- data/lib/rspec_tracer/reporters/coverage_json_reporter.rb +338 -0
- data/lib/rspec_tracer/reporters/html/.gitignore +19 -0
- data/lib/rspec_tracer/reporters/html/.prettierignore +4 -0
- data/lib/rspec_tracer/reporters/html/.prettierrc.json +9 -0
- data/lib/rspec_tracer/reporters/html/README.md +80 -0
- data/lib/rspec_tracer/reporters/html/dist/assets/index.css +2 -0
- data/lib/rspec_tracer/reporters/html/dist/assets/index.js +1 -0
- data/lib/rspec_tracer/reporters/html/dist/index.html +24 -0
- data/lib/rspec_tracer/reporters/html/eslint.config.js +62 -0
- data/lib/rspec_tracer/reporters/html/package-lock.json +4941 -0
- data/lib/rspec_tracer/reporters/html/package.json +29 -0
- data/lib/rspec_tracer/reporters/html/src/app.jsx +130 -0
- data/lib/rspec_tracer/reporters/html/src/components/AllExamples.jsx +86 -0
- data/lib/rspec_tracer/reporters/html/src/components/DuplicateExamples.jsx +68 -0
- data/lib/rspec_tracer/reporters/html/src/components/ExamplesDependency.jsx +78 -0
- data/lib/rspec_tracer/reporters/html/src/components/FilesDependency.jsx +72 -0
- data/lib/rspec_tracer/reporters/html/src/components/FlakyExamples.jsx +42 -0
- data/lib/rspec_tracer/reporters/html/src/components/ReportTable.jsx +131 -0
- data/lib/rspec_tracer/reporters/html/src/components/SearchBar.jsx +19 -0
- data/lib/rspec_tracer/reporters/html/src/index.html +23 -0
- data/lib/rspec_tracer/reporters/html/src/main.jsx +37 -0
- data/lib/rspec_tracer/reporters/html/src/styles.css +434 -0
- data/lib/rspec_tracer/reporters/html/vite.config.js +42 -0
- data/lib/rspec_tracer/reporters/html_reporter.rb +266 -0
- data/lib/rspec_tracer/reporters/json_reporter.rb +88 -0
- data/lib/rspec_tracer/reporters/payload_builder.rb +235 -0
- data/lib/rspec_tracer/reporters/registry.rb +120 -0
- data/lib/rspec_tracer/reporters/terminal_reporter.rb +264 -0
- data/lib/rspec_tracer/rspec/README.md +73 -0
- data/lib/rspec_tracer/rspec/installation.rb +97 -0
- data/lib/rspec_tracer/rspec/metadata.rb +96 -0
- data/lib/rspec_tracer/rspec/parallel_tests.rb +459 -0
- data/lib/rspec_tracer/rspec/reporter_hook.rb +84 -0
- data/lib/rspec_tracer/rspec/runner_hook.rb +178 -0
- data/lib/rspec_tracer/source_file.rb +24 -7
- data/lib/rspec_tracer/storage/README.md +35 -0
- data/lib/rspec_tracer/storage/backend.rb +68 -0
- data/lib/rspec_tracer/storage/json_backend.rb +866 -0
- data/lib/rspec_tracer/storage/lazy_snapshot.rb +65 -0
- data/lib/rspec_tracer/storage/schema.rb +43 -0
- data/lib/rspec_tracer/storage/serializer/json.rb +41 -0
- data/lib/rspec_tracer/storage/serializer/msgpack.rb +90 -0
- data/lib/rspec_tracer/storage/snapshot.rb +127 -0
- data/lib/rspec_tracer/storage/sqlite_backend.rb +686 -0
- data/lib/rspec_tracer/time_formatter.rb +37 -18
- data/lib/rspec_tracer/tracker/README.md +36 -0
- data/lib/rspec_tracer/tracker/coverage_adapter.rb +174 -0
- data/lib/rspec_tracer/tracker/declared_globs.rb +100 -0
- data/lib/rspec_tracer/tracker/dependency_graph.rb +134 -0
- data/lib/rspec_tracer/tracker/env_matcher.rb +127 -0
- data/lib/rspec_tracer/tracker/env_snapshot.rb +77 -0
- data/lib/rspec_tracer/tracker/example_registry.rb +153 -0
- data/lib/rspec_tracer/tracker/file_digest.rb +61 -0
- data/lib/rspec_tracer/tracker/filter.rb +127 -0
- data/lib/rspec_tracer/tracker/input.rb +99 -0
- data/lib/rspec_tracer/tracker/io_hooks/file.rb +55 -0
- data/lib/rspec_tracer/tracker/io_hooks/io.rb +24 -0
- data/lib/rspec_tracer/tracker/io_hooks/json.rb +23 -0
- data/lib/rspec_tracer/tracker/io_hooks/kernel.rb +26 -0
- data/lib/rspec_tracer/tracker/io_hooks/yaml.rb +38 -0
- data/lib/rspec_tracer/tracker/io_hooks.rb +195 -0
- data/lib/rspec_tracer/tracker/loaded_files_tracker.rb +295 -0
- data/lib/rspec_tracer/tracker/new_file_detector.rb +62 -0
- data/lib/rspec_tracer/tracker/whole_suite_invalidators.rb +96 -0
- data/lib/rspec_tracer/version.rb +4 -1
- data/lib/rspec_tracer.rb +232 -381
- metadata +93 -43
- data/lib/rspec_tracer/cache.rb +0 -207
- data/lib/rspec_tracer/coverage_merger.rb +0 -42
- data/lib/rspec_tracer/coverage_reporter.rb +0 -187
- data/lib/rspec_tracer/coverage_writer.rb +0 -58
- data/lib/rspec_tracer/html_reporter/Rakefile +0 -18
- data/lib/rspec_tracer/html_reporter/assets/javascripts/application.js +0 -56
- data/lib/rspec_tracer/html_reporter/assets/javascripts/libraries/jquery.js +0 -10881
- data/lib/rspec_tracer/html_reporter/assets/javascripts/plugins/datatables.js +0 -15381
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/application.css +0 -196
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/datatables.css +0 -459
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/plugins/jquery-ui.css +0 -436
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/print.css +0 -92
- data/lib/rspec_tracer/html_reporter/assets/stylesheets/reset.css +0 -265
- data/lib/rspec_tracer/html_reporter/public/application.css +0 -5
- data/lib/rspec_tracer/html_reporter/public/application.js +0 -6
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_asc_disabled.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_both.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/datatables/images/sort_desc_disabled.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/favicon.png +0 -0
- data/lib/rspec_tracer/html_reporter/public/loading.gif +0 -0
- data/lib/rspec_tracer/html_reporter/reporter.rb +0 -242
- data/lib/rspec_tracer/html_reporter/views/duplicate_examples.erb +0 -34
- data/lib/rspec_tracer/html_reporter/views/examples.erb +0 -58
- data/lib/rspec_tracer/html_reporter/views/examples_dependency.erb +0 -36
- data/lib/rspec_tracer/html_reporter/views/files_dependency.erb +0 -36
- data/lib/rspec_tracer/html_reporter/views/flaky_examples.erb +0 -38
- data/lib/rspec_tracer/html_reporter/views/layout.erb +0 -38
- data/lib/rspec_tracer/remote_cache/aws.rb +0 -176
- data/lib/rspec_tracer/remote_cache/cache.rb +0 -75
- data/lib/rspec_tracer/remote_cache/repo.rb +0 -210
- data/lib/rspec_tracer/report_generator.rb +0 -158
- data/lib/rspec_tracer/report_merger.rb +0 -68
- data/lib/rspec_tracer/report_writer.rb +0 -141
- data/lib/rspec_tracer/reporter.rb +0 -204
- data/lib/rspec_tracer/rspec_reporter.rb +0 -41
- data/lib/rspec_tracer/rspec_runner.rb +0 -56
- data/lib/rspec_tracer/ruby_coverage.rb +0 -9
- 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
|