Skip to content

Commit 9c7bbbf

Browse files
authored
Merge pull request #42 from figma/eliu/skip-known-flaky-test-retries
flaky_tests: Skip requeuing known flaky tests
2 parents ea0fb3a + f36986a commit 9c7bbbf

8 files changed

Lines changed: 106 additions & 4 deletions

File tree

.github/workflows/tests.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,26 @@ jobs:
3131
uses: ruby/setup-ruby@v1
3232
with:
3333
ruby-version: ${{ matrix.ruby }}
34+
- name: Add MiniTest compatibility shim
35+
run: |
36+
cat << 'EOF' > ruby/test/minitest_compat.rb
37+
# Compatibility shim for legacy MiniTest constant (pre-5.0)
38+
require 'minitest'
39+
unless defined?(MiniTest)
40+
MiniTest = Minitest
41+
end
42+
EOF
43+
# Add require to files
44+
sed -i '5i require_relative "minitest_compat"' ruby/test/test_helper.rb
45+
sed -i '5i require_relative "../../test/minitest_compat"' ruby/lib/minitest/queue.rb
3446
- name: Run Ruby tests
3547
run: |
3648
bin/before-install
3749
bin/test
3850
env:
3951
SUITE: ruby
4052
REDIS_HOST: localhost
53+
RUBYOPT: "-W0"
4154

4255
python-tests:
4356
runs-on: ubuntu-latest

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ Two implementations are provided, please refer to the respective documentations:
3737
- [Python](python/)
3838
- [Ruby](ruby/)
3939

40+
## Figma Installation Instructions
41+
42+
Once you have merged your changes into the ci-queue repo, copy the SHAs and pin it in the package.json for integration-test if its an interaction-tests ci-queue update or in the Gemfile in the repo root for sinatra.
43+
4044
## Redis Requirements
4145

4246
`ci-queue` expects the Redis server to have an [eviction policy](https://redis.io/docs/manual/eviction/#eviction-policies) of `allkeys-lru`.

ruby/Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ source 'https://rubygems.org'
44
gemspec
55

66
gem 'activesupport', '~> 5.2.0'
7+
gem 'rexml'

ruby/lib/ci/queue/configuration.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# frozen_string_literal: true
2+
require 'json'
3+
24
module CI
35
module Queue
46
class Configuration
@@ -19,6 +21,7 @@ def from_env(env)
1921
flaky_tests: load_flaky_tests(env['CI_QUEUE_FLAKY_TESTS']),
2022
statsd_endpoint: env['CI_QUEUE_STATSD_ADDR'],
2123
redis_ttl: env['CI_QUEUE_REDIS_TTL']&.to_i || 8 * 60 * 60,
24+
known_flaky_tests: load_known_flaky_tests(env['CI_QUEUE_KNOWN_FLAKY_TESTS']),
2225
)
2326
end
2427

@@ -28,6 +31,18 @@ def load_flaky_tests(path)
2831
rescue SystemCallError
2932
[]
3033
end
34+
35+
def load_known_flaky_tests(path)
36+
if path == nil
37+
return []
38+
end
39+
json_data = JSON.parse(::File.read(path))
40+
known_flaky_test_ids = json_data.map { |test| "#{test['testSuite']}##{test['testName']}" }
41+
puts "ci-queue: Loaded #{known_flaky_test_ids.size} known flaky tests. These will be skipped from requeueing"
42+
known_flaky_test_ids.to_set
43+
rescue SystemCallError, JSON::ParserError, TypeError
44+
[]
45+
end
3146
end
3247

3348
def initialize(
@@ -36,12 +51,13 @@ def initialize(
3651
grind_count: nil, max_duration: nil, failure_file: nil, max_test_duration: nil,
3752
max_test_duration_percentile: 0.5, track_test_duration: false, max_test_failed: nil,
3853
queue_init_timeout: nil, redis_ttl: 8 * 60 * 60, report_timeout: nil, inactive_workers_timeout: nil,
39-
export_flaky_tests_file: nil
54+
export_flaky_tests_file: nil, known_flaky_tests: []
4055
)
4156
@build_id = build_id
4257
@circuit_breakers = [CircuitBreaker::Disabled]
4358
@failure_file = failure_file
4459
@flaky_tests = flaky_tests
60+
@known_flaky_tests = known_flaky_tests
4561
@grind_count = grind_count
4662
@max_requeues = max_requeues
4763
@max_test_duration = max_test_duration
@@ -91,6 +107,10 @@ def flaky?(test)
91107
@flaky_tests.include?(test.id)
92108
end
93109

110+
def known_flaky?(id)
111+
@known_flaky_tests.include?(id)
112+
end
113+
94114
def seed
95115
@seed || build_id
96116
end

ruby/lib/ci/queue/redis/worker.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def requeue(test, offset: Redis.requeue_offset)
110110
raise_on_mismatching_test(test_key)
111111
global_max_requeues = config.global_max_requeues(total)
112112

113-
requeued = config.max_requeues > 0 && global_max_requeues > 0 && eval_script(
113+
requeued = config.max_requeues > 0 && global_max_requeues > 0 && !config.known_flaky?(test_key) && eval_script(
114114
:requeue,
115115
keys: [
116116
key('processed'),

ruby/lib/ci/queue/static.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def requeue(test)
106106
attr_reader :index
107107

108108
def should_requeue?(key)
109-
requeues[key] < config.max_requeues && requeues.values.inject(0, :+) < config.global_max_requeues(total)
109+
requeues[key] < config.max_requeues && requeues.values.inject(0, :+) < config.global_max_requeues(total) && !config.known_flaky?(key)
110110
end
111111

112112
def requeues

ruby/lib/minitest/queue.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ def run_from_queue(reporter, *)
241241
end
242242

243243
requeued = false
244-
if failed && CI::Queue.requeueable?(result) && queue.requeue(example)
244+
if failed && CI::Queue.requeueable?(result) && !queue.config.known_flaky?(example.id) && queue.requeue(example)
245245
requeued = true
246246
result.requeue!
247247
reporter.record(result)

ruby/test/ci/queue/configuration_test.rb

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# frozen_string_literal: true
22
require 'test_helper'
3+
require 'tempfile'
4+
require 'ostruct'
5+
require 'json'
36

47
module CI::Queue
58
class ConfigurationTest < Minitest::Test
@@ -128,5 +131,66 @@ def test_report_timeout_set
128131
assert_equal 45, config.report_timeout
129132
end
130133

134+
def test_load_known_flaky_tests_with_valid_file
135+
Tempfile.open('known_flaky_tests.json') do |file|
136+
test_data = [
137+
{ 'testSuite' => 'TestClass1', 'testName' => 'test_method1' },
138+
{ 'testSuite' => 'TestClass2', 'testName' => 'test_method2' }
139+
]
140+
file.write(test_data.to_json)
141+
file.close
142+
143+
known_flaky_tests = Configuration.load_known_flaky_tests(file.path)
144+
assert_includes known_flaky_tests, 'TestClass1#test_method1'
145+
assert_includes known_flaky_tests, 'TestClass2#test_method2'
146+
assert_equal 2, known_flaky_tests.size
147+
end
148+
end
149+
150+
def test_load_known_flaky_tests_with_missing_file
151+
known_flaky_tests = Configuration.load_known_flaky_tests('/tmp/does-not-exist.json')
152+
assert_empty known_flaky_tests
153+
154+
known_flaky_tests = Configuration.load_known_flaky_tests(nil)
155+
assert_empty known_flaky_tests
156+
end
157+
158+
def test_load_known_flaky_tests_with_invalid_json
159+
Tempfile.open('invalid.json') do |file|
160+
file.write('{ invalid json }')
161+
file.close
162+
163+
known_flaky_tests = Configuration.load_known_flaky_tests(file.path)
164+
assert_empty known_flaky_tests
165+
end
166+
end
167+
168+
def test_known_flaky_method
169+
known_flaky_tests = Set.new(['TestClass1#test_method1', 'TestClass2#test_method2'])
170+
config = Configuration.new(known_flaky_tests: known_flaky_tests)
171+
172+
assert config.known_flaky?('TestClass1#test_method1')
173+
assert config.known_flaky?('TestClass2#test_method2')
174+
refute config.known_flaky?('TestClass3#test_method3')
175+
end
176+
177+
def test_from_env_with_known_flaky_tests
178+
Tempfile.open('known_flaky_tests.json') do |file|
179+
test_data = [
180+
{ 'testSuite' => 'TestClass1', 'testName' => 'test_method1' },
181+
{ 'testSuite' => 'TestClass2', 'testName' => 'test_method2' }
182+
]
183+
file.write(test_data.to_json)
184+
file.close
185+
186+
env = { 'CI_QUEUE_KNOWN_FLAKY_TESTS' => file.path }
187+
config = Configuration.from_env(env)
188+
189+
assert config.known_flaky?('TestClass1#test_method1')
190+
assert config.known_flaky?('TestClass2#test_method2')
191+
refute config.known_flaky?('TestClass3#test_method3')
192+
end
193+
end
194+
131195
end
132196
end

0 commit comments

Comments
 (0)