rspec-tracer 0.7.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,8 +2,8 @@
2
2
 
3
3
  module RSpecTracer
4
4
  class Cache
5
- attr_reader :all_examples, :flaky_examples, :failed_examples, :pending_examples,
6
- :all_files, :dependency, :run_id
5
+ attr_reader :all_examples, :interrupted_examples, :flaky_examples, :failed_examples,
6
+ :pending_examples, :all_files, :dependency, :run_id
7
7
 
8
8
  def initialize
9
9
  @run_id = last_run_id
@@ -12,6 +12,7 @@ module RSpecTracer
12
12
  @cached = false
13
13
 
14
14
  @all_examples = {}
15
+ @interrupted_examples = Set.new
15
16
  @flaky_examples = Set.new
16
17
  @failed_examples = Set.new
17
18
  @pending_examples = Set.new
@@ -74,7 +75,11 @@ module RSpecTracer
74
75
  end
75
76
 
76
77
  @all_examples.each_value do |example|
77
- example[:execution_result].transform_keys!(&:to_sym)
78
+ if example.key?(:execution_result)
79
+ example[:execution_result].transform_keys!(&:to_sym)
80
+ else
81
+ @interrupted_examples << example[:example_id]
82
+ end
78
83
 
79
84
  example[:run_reason] = nil
80
85
  end
@@ -14,21 +14,18 @@ module RSpecTracer
14
14
  return @root if defined?(@root) && root.nil?
15
15
 
16
16
  @cache_path = nil
17
+ @report_path = nil
18
+ @coverage_path = nil
19
+
17
20
  @root = File.expand_path(root || Dir.getwd)
18
21
  end
19
22
 
20
- def project_name(proj_name = nil)
21
- return @project_name if defined?(@project_name) && proj_name.nil?
22
-
23
- @project_name = proj_name if proj_name.is_a?(String)
23
+ def project_name
24
24
  @project_name ||= File.basename(root).capitalize
25
25
  end
26
26
 
27
- def cache_dir(dir = nil)
28
- return @cache_dir if defined?(@cache_dir) && dir.nil?
29
-
30
- @cache_path = nil
31
- @cache_dir = dir || DEFAULT_CACHE_DIR
27
+ def cache_dir
28
+ @cache_dir ||= (ENV['RSPEC_TRACER_CACHE_DIR'] || DEFAULT_CACHE_DIR)
32
29
  end
33
30
 
34
31
  def cache_path
@@ -42,15 +39,12 @@ module RSpecTracer
42
39
  end
43
40
  end
44
41
 
45
- def report_dir(dir = nil)
46
- return @report_dir if defined?(@report_dir) && dir.nil?
47
-
48
- @report_path = nil
49
- @report_dir = dir || DEFAULT_REPORT_DIR
42
+ def report_dir
43
+ @report_dir ||= (ENV['RSPEC_TRACER_REPORT_DIR'] || DEFAULT_REPORT_DIR)
50
44
  end
51
45
 
52
46
  def report_path
53
- @report_path || begin
47
+ @report_path ||= begin
54
48
  report_path = File.expand_path(report_dir, root)
55
49
  report_path = File.join(report_path, ENV['TEST_SUITE_ID'].to_s)
56
50
 
@@ -60,11 +54,8 @@ module RSpecTracer
60
54
  end
61
55
  end
62
56
 
63
- def coverage_dir(dir = nil)
64
- return @coverage_dir if defined?(@coverage_dir) && dir.nil?
65
-
66
- @coverage_path = nil
67
- @coverage_dir = dir || DEFAULT_COVERAGE_DIR
57
+ def coverage_dir
58
+ @coverage_dir ||= (ENV['RSPEC_TRACER_COVERAGE_DIR'] || DEFAULT_COVERAGE_DIR)
68
59
  end
69
60
 
70
61
  def coverage_path
@@ -103,9 +94,7 @@ module RSpecTracer
103
94
  end
104
95
 
105
96
  def verbose?
106
- return @verbose if defined?(@verbose)
107
-
108
- @verbose = ENV.fetch('RSPEC_TRACER_VERBOSE', 'false') == 'true'
97
+ @verbose ||= (ENV.fetch('RSPEC_TRACER_VERBOSE', 'false') == 'true')
109
98
  end
110
99
 
111
100
  def configure(&block)
@@ -60,7 +60,19 @@ module RSpecTracer
60
60
  id: example_id,
61
61
  description: example[:full_description],
62
62
  location: example_location(example[:rerun_file_name], example[:rerun_line_number]),
63
- status: example[:run_reason] || 'Skipped',
63
+ status: example[:run_reason] || 'Skipped'
64
+ }.merge(example_result(example_id, example))
65
+ end
66
+ end
67
+
68
+ def example_result(example_id, example)
69
+ if example[:execution_result].nil?
70
+ {
71
+ result: @reporter.example_interrupted?(example_id) ? 'Interrupted' : '_',
72
+ last_run: '_'
73
+ }
74
+ else
75
+ {
64
76
  result: example[:execution_result][:status].capitalize,
65
77
  last_run: example_run_local_time(example[:execution_result][:finished_at])
66
78
  }
@@ -176,7 +188,7 @@ module RSpecTracer
176
188
 
177
189
  def example_status_css_class(example_status)
178
190
  case example_status.split.first
179
- when 'Failed', 'Flaky'
191
+ when 'Failed', 'Flaky', 'Interrupted'
180
192
  'red'
181
193
  when 'Pending'
182
194
  'yellow'
@@ -189,7 +201,7 @@ module RSpecTracer
189
201
  case example_result
190
202
  when 'Passed'
191
203
  'green'
192
- when 'Failed'
204
+ when 'Failed', 'Interrupted'
193
205
  'red'
194
206
  when 'Pending'
195
207
  'yellow'
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpecTracer
4
+ module RemoteCache
5
+ class Aws
6
+ class AwsError < StandardError; end
7
+
8
+ def initialize
9
+ @s3_bucket, @s3_path = setup_s3
10
+ @aws_cli = setup_aws_cli
11
+ end
12
+
13
+ def branch_refs?(branch_name)
14
+ key = "#{@s3_path}/branch-refs/#{branch_name}/branch_refs.json"
15
+
16
+ system(
17
+ @aws_cli,
18
+ 's3api',
19
+ 'head-object',
20
+ '--bucket',
21
+ @s3_bucket,
22
+ '--key',
23
+ key,
24
+ out: File::NULL,
25
+ err: File::NULL
26
+ )
27
+ end
28
+
29
+ def download_branch_refs(branch_name, file_name)
30
+ key = "#{@s3_path}/branch-refs/#{branch_name}/branch_refs.json"
31
+
32
+ system(
33
+ @aws_cli,
34
+ 's3api',
35
+ 'get-object',
36
+ '--bucket',
37
+ @s3_bucket,
38
+ '--key',
39
+ key,
40
+ file_name,
41
+ out: File::NULL,
42
+ err: File::NULL
43
+ )
44
+ end
45
+
46
+ def upload_branch_refs(branch_name, file_name)
47
+ remote_path = "s3://#{@s3_bucket}/#{@s3_path}/branch-refs/#{branch_name}/branch_refs.json"
48
+
49
+ raise AwsError, "Failed to upload branch refs for #{branch_name} branch" unless system(
50
+ @aws_cli,
51
+ 's3',
52
+ 'cp',
53
+ file_name,
54
+ remote_path,
55
+ out: File::NULL,
56
+ err: File::NULL
57
+ )
58
+
59
+ puts "Uploaded branch refs for #{branch_name} branch to #{remote_path}"
60
+ end
61
+
62
+ def cache_files_list(ref)
63
+ prefix = "s3://#{@s3_bucket}/#{@s3_path}/#{ref}/"
64
+
65
+ `#{@aws_cli} s3 ls #{prefix} --recursive`.chomp.split("\n")
66
+ end
67
+
68
+ def download_file(ref, file_name)
69
+ remote_path = File.join(s3_dir(ref), file_name)
70
+ local_path = File.join(RSpecTracer.cache_path, file_name)
71
+
72
+ raise AwsError, "Failed to download file #{remote_path}" unless system(
73
+ @aws_cli,
74
+ 's3',
75
+ 'cp',
76
+ remote_path,
77
+ local_path,
78
+ out: File::NULL,
79
+ err: File::NULL
80
+ )
81
+
82
+ puts "Downloaded file #{remote_path} to #{local_path}"
83
+ end
84
+
85
+ def download_dir(ref, run_id)
86
+ remote_dir = s3_dir(ref, run_id)
87
+ local_dir = File.join(RSpecTracer.cache_path, run_id)
88
+
89
+ raise AwsError, "Failed to download files from #{remote_dir}" unless system(
90
+ @aws_cli,
91
+ 's3',
92
+ 'cp',
93
+ remote_dir,
94
+ local_dir,
95
+ '--recursive',
96
+ out: File::NULL,
97
+ err: File::NULL
98
+ )
99
+
100
+ puts "Downloaded cache files from #{remote_dir} to #{local_dir}"
101
+ rescue AwsError => e
102
+ FileUtils.rm_rf(local_dir)
103
+
104
+ raise e
105
+ end
106
+
107
+ def upload_file(ref, file_name)
108
+ remote_path = File.join(s3_dir(ref), file_name)
109
+ local_path = File.join(RSpecTracer.cache_path, file_name)
110
+
111
+ raise AwsError, "Failed to upload file #{local_path}" unless system(
112
+ @aws_cli,
113
+ 's3',
114
+ 'cp',
115
+ local_path,
116
+ remote_path,
117
+ out: File::NULL,
118
+ err: File::NULL
119
+ )
120
+
121
+ puts "Uploaded file #{local_path} to #{remote_path}"
122
+ end
123
+
124
+ def upload_dir(ref, run_id)
125
+ remote_dir = s3_dir(ref, run_id)
126
+ local_dir = File.join(RSpecTracer.cache_path, run_id)
127
+
128
+ raise AwsError, "Failed to download files from #{local_dir}" unless system(
129
+ @aws_cli,
130
+ 's3',
131
+ 'cp',
132
+ local_dir,
133
+ remote_dir,
134
+ '--recursive',
135
+ out: File::NULL,
136
+ err: File::NULL
137
+ )
138
+
139
+ puts "Uploaded files from #{local_dir} to #{remote_dir}"
140
+ end
141
+
142
+ private
143
+
144
+ def setup_s3
145
+ s3_uri = ENV['RSPEC_TRACER_S3_URI']
146
+
147
+ raise AwsError, 'RSPEC_TRACER_S3_URI environment variable is not set' if s3_uri.nil?
148
+
149
+ uri_parts = s3_uri[4..-1].split('/')
150
+
151
+ raise AwsError, "Invalid S3 URI #{s3_uri}" unless uri_parts.length >= 3 && uri_parts.first.empty?
152
+
153
+ [
154
+ uri_parts[1],
155
+ uri_parts[2..-1].join('/')
156
+ ]
157
+ end
158
+
159
+ def setup_aws_cli
160
+ if ENV.fetch('LOCAL_AWS', 'false') == 'true'
161
+ 'awslocal'
162
+ else
163
+ 'aws'
164
+ end
165
+ end
166
+
167
+ def s3_dir(ref, run_id = nil)
168
+ test_suite_id = ENV['TEST_SUITE_ID']
169
+
170
+ if test_suite_id.nil?
171
+ "s3://#{@s3_bucket}/#{@s3_path}/#{ref}/#{run_id}/".sub(%r{/+$}, '/')
172
+ else
173
+ "s3://#{@s3_bucket}/#{@s3_path}/#{ref}/#{test_suite_id}/#{run_id}/".sub(%r{/+$}, '/')
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -1,186 +1,78 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'git'
3
+ require_relative 'aws'
4
+ require_relative 'repo'
5
+ require_relative 'validator'
4
6
 
5
7
  module RSpecTracer
6
8
  module RemoteCache
7
9
  class Cache
8
- class CacheDownloadError < StandardError; end
9
-
10
- class CacheUploadError < StandardError; end
11
-
12
- class LocalCacheNotFoundError < StandardError; end
13
-
14
- CACHE_FILES_PER_TEST_SUITE = 8
10
+ class CacheError < StandardError; end
15
11
 
16
12
  def initialize
17
- @s3_uri = ENV['RSPEC_TRACER_S3_URI']
18
- @aws_s3 = if ENV.fetch('LOCAL_AWS', 'false') == 'true'
19
- 'awslocal'
20
- else
21
- 'aws'
22
- end
13
+ @aws = RSpecTracer::RemoteCache::Aws.new
14
+ @repo = RSpecTracer::RemoteCache::Repo.new(@aws)
23
15
  end
24
16
 
25
17
  def download
26
- if @s3_uri.nil?
27
- puts 'S3 URI is not configured'
28
-
29
- return
30
- end
18
+ return unless cache_ref?
31
19
 
32
- prepare_for_download
33
-
34
- if @cache_sha.nil?
35
- puts 'Could not find a suitable cache sha to download'
36
-
37
- return
38
- end
39
-
40
- download_files
41
-
42
- puts "Downloaded cache from #{@download_prefix} to #{@download_path}"
20
+ @aws.download_file(@cache_sha, 'last_run.json')
21
+ @aws.download_dir(@cache_sha, last_run_id)
43
22
  rescue StandardError => e
44
- puts "Errored: #{e.message}"
23
+ puts "Error: #{e.message}"
24
+ puts e.backtrace.first(5).join("\n")
45
25
  end
46
26
 
47
27
  def upload
48
- if @s3_uri.nil?
49
- puts 'S3 URI is not configured'
28
+ @aws.upload_file(@repo.branch_ref, 'last_run.json')
29
+ @aws.upload_dir(@repo.branch_ref, last_run_id)
50
30
 
51
- return
52
- end
31
+ file_name = File.join(RSpecTracer.cache_path, 'branch_refs.json')
53
32
 
54
- prepare_for_upload
55
- upload_files
56
-
57
- puts "Uploaded cache from #{@upload_path} to #{@upload_prefix}"
58
- rescue CacheUploadError => e
59
- puts "Errored: #{e.message}"
33
+ write_branch_refs(file_name)
34
+ @aws.upload_branch_refs(@repo.branch_name, file_name)
35
+ rescue StandardError => e
36
+ puts "Error: #{e.message}"
37
+ puts e.backtrace.first(5).join("\n")
60
38
  end
61
39
 
62
40
  private
63
41
 
64
- def prepare_for_download
65
- @test_suite_id = ENV['TEST_SUITE_ID']
66
- @test_suites = ENV['TEST_SUITES']
42
+ def cache_ref?
43
+ cache_validator = RSpecTracer::RemoteCache::Validator.new
67
44
 
68
- if @test_suite_id.nil? ^ @test_suites.nil?
69
- raise(
70
- CacheDownloadError,
71
- 'Both the enviornment variables TEST_SUITE_ID and TEST_SUITES are not set'
72
- )
73
- end
74
-
75
- @git = RSpecTracer::RemoteCache::Git.new
76
- @git.prepare_for_download
45
+ @cache_sha = @repo.cache_refs.each_key.detect do |ref|
46
+ puts "Validating ref #{ref}"
77
47
 
78
- generate_cached_files_count_and_regex
79
-
80
- @cache_sha = nearest_cache_sha
81
- end
82
-
83
- def generate_cached_files_count_and_regex
84
- if @test_suites.nil?
85
- @last_run_files_count = 1
86
- @last_run_files_regex = '/%<ref>s/last_run.json$'
87
- @cached_files_count = CACHE_FILES_PER_TEST_SUITE
88
- @cached_files_regex = '/%<ref>s/[0-9a-f]{32}/.+.json'
89
- else
90
- @test_suites = @test_suites.to_i
91
- @test_suites_regex = (1..@test_suites).to_a.join('|')
92
-
93
- @last_run_files_count = @test_suites
94
- @last_run_files_regex = "/%<ref>s/(#{@test_suites_regex})/last_run.json$"
95
- @cached_files_count = CACHE_FILES_PER_TEST_SUITE * @test_suites.to_i
96
- @cached_files_regex = "/%<ref>s/(#{@test_suites_regex})/[0-9a-f]{32}/.+.json$"
48
+ cache_validator.valid?(ref, @aws.cache_files_list(ref))
97
49
  end
98
- end
99
-
100
- def nearest_cache_sha
101
- @git.ref_list.detect do |ref|
102
- prefix = "#{@s3_uri}/#{ref}/"
103
-
104
- puts "Testing prefix #{prefix}"
105
-
106
- objects = `#{@aws_s3} s3 ls #{prefix} --recursive`.chomp.split("\n")
107
50
 
108
- last_run_regex = Regexp.new(format(@last_run_files_regex, ref: ref))
109
-
110
- next if objects.count { |object| object.match?(last_run_regex) } != @last_run_files_count
111
-
112
- cache_regex = Regexp.new(format(@cached_files_regex, ref: ref))
51
+ if @cache_sha.nil?
52
+ puts 'Could not find a suitable cache sha to download'
113
53
 
114
- objects.count { |object| object.match?(cache_regex) } == @cached_files_count
54
+ return false
115
55
  end
116
- end
117
-
118
- def download_files
119
- @download_prefix = "#{@s3_uri}/#{@cache_sha}/#{@test_suite_id}/".sub(%r{/+$}, '/')
120
- @download_path = RSpecTracer.cache_path
121
-
122
- raise CacheDownloadError, 'Failed to download cache files' unless system(
123
- @aws_s3, 's3', 'cp',
124
- File.join(@download_prefix, 'last_run.json'),
125
- @download_path,
126
- out: File::NULL, err: File::NULL
127
- )
128
56
 
129
- @run_id = last_run_id
57
+ true
58
+ end
130
59
 
131
- return if system(
132
- @aws_s3, 's3', 'cp',
133
- File.join(@download_prefix, @run_id),
134
- File.join(@download_path, @run_id),
135
- '--recursive',
136
- out: File::NULL, err: File::NULL
137
- )
60
+ def write_branch_refs(file_name)
61
+ branch_ref_time = `git show --no-patch --format="%ct" #{@repo.branch_ref}`.chomp
138
62
 
139
- FileUtils.rm_rf(@download_path)
63
+ puts "Failed to find object #{@repo.branch_ref} commit timestamp" unless $CHILD_STATUS.success?
140
64
 
141
- raise CacheDownloadError, 'Failed to download cache files'
142
- end
65
+ ref_list = @repo.branch_refs.merge(@repo.branch_ref => branch_ref_time.to_i)
143
66
 
144
- def prepare_for_upload
145
- @git = RSpecTracer::RemoteCache::Git.new
146
- @test_suite_id = ENV['TEST_SUITE_ID']
147
- @upload_prefix = if @test_suite_id.nil?
148
- "#{@s3_uri}/#{@git.branch_ref}/"
149
- else
150
- "#{@s3_uri}/#{@git.branch_ref}/#{@test_suite_id}/"
151
- end
152
-
153
- @upload_path = RSpecTracer.cache_path
154
- @run_id = last_run_id
155
- end
156
-
157
- def upload_files
158
- return if system(
159
- @aws_s3, 's3', 'cp',
160
- File.join(@upload_path, 'last_run.json'),
161
- @upload_prefix,
162
- out: File::NULL, err: File::NULL
163
- ) && system(
164
- @aws_s3, 's3', 'cp',
165
- File.join(@upload_path, @run_id),
166
- File.join(@upload_prefix, @run_id),
167
- '--recursive',
168
- out: File::NULL, err: File::NULL
169
- )
170
-
171
- raise CacheUploadError, 'Failed to upload cache files'
67
+ File.write(file_name, JSON.pretty_generate(ref_list))
172
68
  end
173
69
 
174
70
  def last_run_id
175
71
  file_name = File.join(RSpecTracer.cache_path, 'last_run.json')
176
72
 
177
- return unless File.file?(file_name)
178
-
179
- run_id = JSON.parse(File.read(file_name))['run_id']
180
-
181
- raise LocalCacheNotFoundError, 'Could not find any local cache to upload' if run_id.nil?
73
+ raise CacheError, 'Could not find any local cache to upload' unless File.file?(file_name)
182
74
 
183
- run_id
75
+ JSON.parse(File.read(file_name))['run_id']
184
76
  end
185
77
  end
186
78
  end