ci-queue 0.11.1 → 0.12.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 +5 -5
- data/.gitignore +2 -0
- data/dev.yml +0 -9
- data/lib/ci/queue/configuration.rb +15 -1
- data/lib/ci/queue/redis/base.rb +4 -0
- data/lib/ci/queue/static.rb +4 -0
- data/lib/ci/queue/version.rb +1 -1
- data/lib/minitest/queue.rb +69 -2
- data/lib/minitest/queue/junit_reporter.rb +113 -0
- data/lib/minitest/{reporters → queue}/order_reporter.rb +1 -1
- data/lib/minitest/queue/runner.rb +4 -2
- data/railgun.yml +13 -0
- metadata +6 -5
- data/lib/minitest/reporters/redis_reporter.rb +0 -91
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 78b51045e571e242b8d4b64e49f37abd462be948f007de6a63380ca0f448ce4c
|
4
|
+
data.tar.gz: bdec2201394129e091516b82e5709a016209aec6a8f41488132ac8400c88150d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e51188516bdd8b04c29981a4cd7f89ac2f6a8a7979c72210065362d34b86565522e3e26305a2c088656d543c064c802c4886f6ad4801f675b7c73cbec82da38c
|
7
|
+
data.tar.gz: 14853c01858461192992d2dd330a96dcb66ca6a9c17100c4d73b4d6b364cb7b1c7e8661b14d8136a454f2d0bf0a72a8af375a07dd6eba91a2541784f6964f1fc
|
data/.gitignore
CHANGED
data/dev.yml
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
|
1
2
|
module CI
|
2
3
|
module Queue
|
3
4
|
class Configuration
|
@@ -9,13 +10,21 @@ module CI
|
|
9
10
|
build_id: env['CIRCLE_BUILD_URL'] || env['BUILDKITE_BUILD_ID'] || env['TRAVIS_BUILD_ID'],
|
10
11
|
worker_id: env['CIRCLE_NODE_INDEX'] || env['BUILDKITE_PARALLEL_JOB'],
|
11
12
|
seed: env['CIRCLE_SHA1'] || env['BUILDKITE_COMMIT'] || env['TRAVIS_COMMIT'],
|
13
|
+
flaky_tests: load_flaky_tests(env['CI_QUEUE_FLAKY_TESTS']),
|
12
14
|
)
|
13
15
|
end
|
16
|
+
|
17
|
+
def load_flaky_tests(path)
|
18
|
+
return [] unless path
|
19
|
+
::File.readlines(path).map(&:chomp).to_set
|
20
|
+
rescue SystemCallError
|
21
|
+
[]
|
22
|
+
end
|
14
23
|
end
|
15
24
|
|
16
25
|
def initialize(
|
17
26
|
timeout: 30, build_id: nil, worker_id: nil, max_requeues: 0, requeue_tolerance: 0,
|
18
|
-
namespace: nil, seed: nil
|
27
|
+
namespace: nil, seed: nil, flaky_tests: []
|
19
28
|
)
|
20
29
|
@namespace = namespace
|
21
30
|
@timeout = timeout
|
@@ -24,6 +33,11 @@ module CI
|
|
24
33
|
@max_requeues = max_requeues
|
25
34
|
@requeue_tolerance = requeue_tolerance
|
26
35
|
@seed = seed
|
36
|
+
@flaky_tests = flaky_tests
|
37
|
+
end
|
38
|
+
|
39
|
+
def flaky?(test)
|
40
|
+
@flaky_tests.include?(test.id)
|
27
41
|
end
|
28
42
|
|
29
43
|
def seed
|
data/lib/ci/queue/redis/base.rb
CHANGED
data/lib/ci/queue/static.rb
CHANGED
data/lib/ci/queue/version.rb
CHANGED
data/lib/minitest/queue.rb
CHANGED
@@ -7,7 +7,8 @@ require 'minitest/queue/error_report'
|
|
7
7
|
require 'minitest/queue/local_requeue_reporter'
|
8
8
|
require 'minitest/queue/build_status_recorder'
|
9
9
|
require 'minitest/queue/build_status_reporter'
|
10
|
-
|
10
|
+
require 'minitest/queue/order_reporter'
|
11
|
+
require 'minitest/queue/junit_reporter'
|
11
12
|
|
12
13
|
module Minitest
|
13
14
|
class Requeue < Skip
|
@@ -35,6 +36,31 @@ module Minitest
|
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
39
|
+
class Flaked < Skip
|
40
|
+
attr_reader :failure
|
41
|
+
|
42
|
+
def initialize(failure)
|
43
|
+
super()
|
44
|
+
@failure = failure
|
45
|
+
end
|
46
|
+
|
47
|
+
def result_label
|
48
|
+
"Skipped"
|
49
|
+
end
|
50
|
+
|
51
|
+
def backtrace
|
52
|
+
failure.backtrace
|
53
|
+
end
|
54
|
+
|
55
|
+
def error
|
56
|
+
failure.error
|
57
|
+
end
|
58
|
+
|
59
|
+
def message
|
60
|
+
failure.message
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
38
64
|
module Requeueing
|
39
65
|
# Make requeues acts as skips for reporters not aware of the difference.
|
40
66
|
def skipped?
|
@@ -50,6 +76,25 @@ module Minitest
|
|
50
76
|
end
|
51
77
|
end
|
52
78
|
|
79
|
+
module Flakiness
|
80
|
+
# Make failed flaky tests acts as skips for reporters not aware of the difference.
|
81
|
+
def skipped?
|
82
|
+
super || flaked?
|
83
|
+
end
|
84
|
+
|
85
|
+
def flaked?
|
86
|
+
!!((Flaked === failure) || @flaky)
|
87
|
+
end
|
88
|
+
|
89
|
+
def mark_as_flaked!
|
90
|
+
if passed?
|
91
|
+
@flaky = true
|
92
|
+
else
|
93
|
+
self.failures.unshift(Flaked.new(self.failures.shift))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
53
98
|
module Queue
|
54
99
|
class SingleExample
|
55
100
|
def initialize(runnable, method_name)
|
@@ -68,6 +113,10 @@ module Minitest
|
|
68
113
|
def run
|
69
114
|
Minitest.run_one_method(@runnable, @method_name)
|
70
115
|
end
|
116
|
+
|
117
|
+
def flaky?
|
118
|
+
Minitest.queue.flaky?(self)
|
119
|
+
end
|
71
120
|
end
|
72
121
|
|
73
122
|
attr_reader :queue
|
@@ -103,6 +152,12 @@ module Minitest
|
|
103
152
|
queue.poll do |example|
|
104
153
|
result = example.run
|
105
154
|
failed = !(result.passed? || result.skipped?)
|
155
|
+
|
156
|
+
if example.flaky?
|
157
|
+
result.mark_as_flaked!
|
158
|
+
failed = false
|
159
|
+
end
|
160
|
+
|
106
161
|
if failed && queue.requeue(example)
|
107
162
|
result.requeue!
|
108
163
|
reporter.record(result)
|
@@ -119,7 +174,19 @@ end
|
|
119
174
|
MiniTest.singleton_class.prepend(MiniTest::Queue)
|
120
175
|
if defined? MiniTest::Result
|
121
176
|
MiniTest::Result.prepend(MiniTest::Requeueing)
|
177
|
+
MiniTest::Result.prepend(MiniTest::Flakiness)
|
122
178
|
else
|
123
179
|
MiniTest::Test.prepend(MiniTest::Requeueing)
|
124
|
-
MiniTest::Test.
|
180
|
+
MiniTest::Test.prepend(MiniTest::Flakiness)
|
181
|
+
|
182
|
+
module MinitestBackwardCompatibility
|
183
|
+
def source_location
|
184
|
+
method(name).source_location
|
185
|
+
end
|
186
|
+
|
187
|
+
def klass
|
188
|
+
self.class.name
|
189
|
+
end
|
190
|
+
end
|
191
|
+
MiniTest::Test.prepend(MinitestBackwardCompatibility)
|
125
192
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'minitest/reporters'
|
2
|
+
require 'builder'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Minitest
|
6
|
+
module Queue
|
7
|
+
class JUnitReporter < Minitest::Reporters::BaseReporter
|
8
|
+
class XmlMarkup < ::Builder::XmlMarkup
|
9
|
+
def trunc!(txt)
|
10
|
+
txt.sub(/\n.*/m, '...')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(report_path = 'log/junit.xml', options = {})
|
15
|
+
super({})
|
16
|
+
@report_path = File.absolute_path(report_path)
|
17
|
+
@base_path = options[:base_path] || Dir.pwd
|
18
|
+
end
|
19
|
+
|
20
|
+
def report
|
21
|
+
super
|
22
|
+
|
23
|
+
suites = tests.group_by { |test| test.klass }
|
24
|
+
|
25
|
+
xml = Builder::XmlMarkup.new(indent: 2)
|
26
|
+
xml.instruct!
|
27
|
+
xml.test_suites do
|
28
|
+
suites.each do |suite, tests|
|
29
|
+
add_tests_to(xml, suite, tests)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
FileUtils.mkdir_p(File.dirname(@report_path))
|
33
|
+
File.open(@report_path, 'w+') { |file| file << xml.target! }
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def add_tests_to(xml, suite, tests)
|
39
|
+
suite_result = analyze_suite(tests)
|
40
|
+
file_path = Pathname.new(tests.first.source_location.first)
|
41
|
+
base_path = Pathname.new(@base_path)
|
42
|
+
relative_path = file_path.relative_path_from(base_path)
|
43
|
+
|
44
|
+
xml.testsuite(name: suite, filepath: relative_path,
|
45
|
+
skipped: suite_result[:skip_count], failures: suite_result[:fail_count],
|
46
|
+
errors: suite_result[:error_count], tests: suite_result[:test_count],
|
47
|
+
assertions: suite_result[:assertion_count], time: suite_result[:time]) do
|
48
|
+
tests.each do |test|
|
49
|
+
lineno = test.source_location.last
|
50
|
+
xml.testcase(name: test.name, lineno: lineno, classname: suite, assertions: test.assertions,
|
51
|
+
time: test.time, flaky_test: test.flaked?) do
|
52
|
+
xml << xml_message_for(test) unless test.passed?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def xml_message_for(test)
|
59
|
+
xml = XmlMarkup.new(indent: 2, margin: 2)
|
60
|
+
error = test.failure
|
61
|
+
|
62
|
+
if test.skipped? && !test.flaked?
|
63
|
+
xml.skipped(type: test.name)
|
64
|
+
elsif test.error?
|
65
|
+
xml.error(type: test.name, message: xml.trunc!(error.message)) do
|
66
|
+
xml.text!(message_for(test))
|
67
|
+
end
|
68
|
+
elsif test.failure
|
69
|
+
xml.failure(type: test.name, message: xml.trunc!(error.message)) do
|
70
|
+
xml.text!(message_for(test))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def message_for(test)
|
76
|
+
suite = test.klass
|
77
|
+
name = test.name
|
78
|
+
error = test.failure
|
79
|
+
|
80
|
+
if test.passed?
|
81
|
+
nil
|
82
|
+
elsif test.skipped?
|
83
|
+
"Skipped:\n#{name}(#{suite}) [#{location(error)}]:\n#{error.message}\n"
|
84
|
+
elsif test.failure
|
85
|
+
"Failure:\n#{name}(#{suite}) [#{location(error)}]:\n#{error.message}\n"
|
86
|
+
elsif test.error?
|
87
|
+
"Error:\n#{name}(#{suite}):\n#{error.message}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def location(exception)
|
92
|
+
last_before_assertion = ''
|
93
|
+
exception.backtrace.reverse_each do |s|
|
94
|
+
break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
|
95
|
+
last_before_assertion = s
|
96
|
+
end
|
97
|
+
last_before_assertion.sub(/:in .*$/, '')
|
98
|
+
end
|
99
|
+
|
100
|
+
def analyze_suite(tests)
|
101
|
+
result = Hash.new(0)
|
102
|
+
result[:time] = 0
|
103
|
+
tests.each do |test|
|
104
|
+
result[:"#{result(test)}_count"] += 1
|
105
|
+
result[:assertion_count] += test.assertions
|
106
|
+
result[:test_count] += 1
|
107
|
+
result[:time] += test.time
|
108
|
+
end
|
109
|
+
result
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -44,8 +44,10 @@ module Minitest
|
|
44
44
|
set_load_path
|
45
45
|
Minitest.queue = queue
|
46
46
|
Minitest.queue_reporters = [
|
47
|
-
|
48
|
-
|
47
|
+
LocalRequeueReporter.new,
|
48
|
+
BuildStatusRecorder.new(build: queue.build),
|
49
|
+
JUnitReporter.new,
|
50
|
+
OrderReporter.new(path: 'log/test_order.log'),
|
49
51
|
]
|
50
52
|
|
51
53
|
trap('TERM') { Minitest.queue.shutdown! }
|
data/railgun.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# This file is for Shopify employees development environment.
|
2
|
+
# If you are an external contributor you don't have to bother with it.
|
3
|
+
name: ci-queue
|
4
|
+
|
5
|
+
vm:
|
6
|
+
image: /opt/dev/misc/railgun-images/default
|
7
|
+
ip_address: 192.168.64.245
|
8
|
+
memory: 1G
|
9
|
+
cores: 2
|
10
|
+
volumes:
|
11
|
+
root: 512M
|
12
|
+
services:
|
13
|
+
- redis
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ci-queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ansi
|
@@ -194,13 +194,14 @@ files:
|
|
194
194
|
- lib/minitest/queue/build_status_reporter.rb
|
195
195
|
- lib/minitest/queue/error_report.rb
|
196
196
|
- lib/minitest/queue/failure_formatter.rb
|
197
|
+
- lib/minitest/queue/junit_reporter.rb
|
197
198
|
- lib/minitest/queue/local_requeue_reporter.rb
|
199
|
+
- lib/minitest/queue/order_reporter.rb
|
198
200
|
- lib/minitest/queue/runner.rb
|
199
201
|
- lib/minitest/reporters/bisect_reporter.rb
|
200
|
-
- lib/minitest/reporters/order_reporter.rb
|
201
|
-
- lib/minitest/reporters/redis_reporter.rb
|
202
202
|
- lib/rspec/queue.rb
|
203
203
|
- lib/rspec/queue/build_status_recorder.rb
|
204
|
+
- railgun.yml
|
204
205
|
homepage: https://github.com/Shopify/ci-queue
|
205
206
|
licenses:
|
206
207
|
- MIT
|
@@ -221,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
221
222
|
version: '0'
|
222
223
|
requirements: []
|
223
224
|
rubyforge_project:
|
224
|
-
rubygems_version: 2.6
|
225
|
+
rubygems_version: 2.7.6
|
225
226
|
signing_key:
|
226
227
|
specification_version: 4
|
227
228
|
summary: Distribute tests over many workers using a queue
|
@@ -1,91 +0,0 @@
|
|
1
|
-
require 'minitest/reporters'
|
2
|
-
|
3
|
-
module Minitest
|
4
|
-
module Reporters
|
5
|
-
module RedisReporter
|
6
|
-
include ANSI::Code
|
7
|
-
|
8
|
-
COUNTERS = %w(
|
9
|
-
assertions
|
10
|
-
errors
|
11
|
-
failures
|
12
|
-
skips
|
13
|
-
requeues
|
14
|
-
total_time
|
15
|
-
).freeze
|
16
|
-
|
17
|
-
class << self
|
18
|
-
attr_accessor :failure_formatter
|
19
|
-
end
|
20
|
-
self.failure_formatter = FailureFormatter
|
21
|
-
|
22
|
-
class Base < BaseReporter
|
23
|
-
def initialize(build:, **options)
|
24
|
-
@build = build
|
25
|
-
super(options)
|
26
|
-
end
|
27
|
-
|
28
|
-
def completed?
|
29
|
-
build.queue_exhausted?
|
30
|
-
end
|
31
|
-
|
32
|
-
def error_reports
|
33
|
-
build.error_reports.sort_by(&:first).map { |k, v| ErrorReport.load(v) }
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
end
|
39
|
-
|
40
|
-
class Summary < Base
|
41
|
-
end
|
42
|
-
|
43
|
-
class Worker < Base
|
44
|
-
attr_accessor :requeues
|
45
|
-
|
46
|
-
def initialize(*)
|
47
|
-
super
|
48
|
-
self.failures = 0
|
49
|
-
self.errors = 0
|
50
|
-
self.skips = 0
|
51
|
-
self.requeues = 0
|
52
|
-
end
|
53
|
-
|
54
|
-
def report
|
55
|
-
# noop
|
56
|
-
end
|
57
|
-
|
58
|
-
def record(test)
|
59
|
-
super
|
60
|
-
|
61
|
-
self.total_time = Minitest.clock_time - start_time
|
62
|
-
if test.requeued?
|
63
|
-
self.requeues += 1
|
64
|
-
elsif test.skipped?
|
65
|
-
self.skips += 1
|
66
|
-
elsif test.error?
|
67
|
-
self.errors += 1
|
68
|
-
elsif test.failure
|
69
|
-
self.failures += 1
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
stats = COUNTERS.zip(COUNTERS.map { |c| send(c) })
|
74
|
-
if (test.failure || test.error?) && !test.skipped?
|
75
|
-
build.record_error("#{test.klass}##{test.name}", dump(test), stats: stats)
|
76
|
-
else
|
77
|
-
build.record_success("#{test.klass}##{test.name}", stats: stats)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def dump(test)
|
84
|
-
ErrorReport.new(RedisReporter.failure_formatter.new(test).to_h).dump
|
85
|
-
end
|
86
|
-
|
87
|
-
attr_reader :aggregates
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|