flaky-friend 0.2.1 → 0.3.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/lib/flaky/commands/fetch.rb +2 -1
- data/lib/flaky/commands/history.rb +4 -3
- data/lib/flaky/commands/rank.rb +1 -0
- data/lib/flaky/commands/stress.rb +44 -4
- data/lib/flaky/database.rb +7 -2
- data/lib/flaky/providers/base.rb +1 -1
- data/lib/flaky/providers/github_actions.rb +2 -1
- data/lib/flaky/providers/semaphore.rb +1 -0
- data/lib/flaky/repository.rb +24 -5
- data/lib/flaky/tasks/flaky.rake +7 -2
- data/lib/flaky/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6216aed376755cc4ce188f8c5dc42b0146d1c7b3397c409c3eef9238eb8b987e
|
|
4
|
+
data.tar.gz: a41b69d5707e4fcb93ebb7bc4bfbefda33ce827c49475a1e8a5d0b85506cba18
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e1fd50da98dfafcf952f79c39b920e7b25e4d381c5f8e3a3afa5dd71cf2eb239a59933163abb7102345aa740aa2ec434b0e94d9e764ab233401cc389bea3ccf
|
|
7
|
+
data.tar.gz: 1b1f57e521913ff056e5c1732eccf76b5fe41b5ae66c25a830330fc01a2e7ad10b2a47d49d79ac211557ae634d86ca7305b67ff068474e34587d99f95d5c0a5e
|
data/lib/flaky/commands/fetch.rb
CHANGED
|
@@ -60,7 +60,8 @@ module Flaky
|
|
|
60
60
|
|
|
61
61
|
@repo.insert_ci_run(
|
|
62
62
|
workflow_id: wf[:id], pipeline_id: wf[:pipeline_id],
|
|
63
|
-
branch: wf[:branch], result: pipeline_result, created_at: wf[:created_at]
|
|
63
|
+
branch: wf[:branch], result: pipeline_result, created_at: wf[:created_at],
|
|
64
|
+
commit_sha: wf[:commit_sha]
|
|
64
65
|
)
|
|
65
66
|
|
|
66
67
|
puts "#{jobs.length} test jobs (#{pipeline_result})"
|
|
@@ -22,11 +22,12 @@ module Flaky
|
|
|
22
22
|
first = rows.first
|
|
23
23
|
puts "Failure history for #{first['spec_file']}:#{first['line_number']}"
|
|
24
24
|
puts " #{first['description']}\n\n"
|
|
25
|
-
puts format("%-20s %-8s %-30s %s", "Date", "Seed", "Job", "Workflow")
|
|
26
|
-
puts "-" *
|
|
25
|
+
puts format("%-20s %-8s %-10s %-30s %s", "Date", "Seed", "Commit", "Job", "Workflow")
|
|
26
|
+
puts "-" * 110
|
|
27
27
|
|
|
28
28
|
rows.each do |row|
|
|
29
|
-
|
|
29
|
+
sha = row["commit_sha"] ? row["commit_sha"][0..6] : " "
|
|
30
|
+
puts format("%-20s %-8d %-10s %-30s %s", row["failed_at"], row["seed"], sha, row["job_name"], row["workflow_id"])
|
|
30
31
|
end
|
|
31
32
|
|
|
32
33
|
puts "\nTotal failures: #{rows.length}"
|
data/lib/flaky/commands/rank.rb
CHANGED
|
@@ -18,13 +18,14 @@ module Flaky
|
|
|
18
18
|
env = {}
|
|
19
19
|
env["FLAKY_CI_SIMULATE"] = "1" if @ci_simulate
|
|
20
20
|
|
|
21
|
+
seeds = resolve_seeds
|
|
21
22
|
passes = 0
|
|
22
23
|
failures = 0
|
|
23
24
|
failed_seeds = []
|
|
24
25
|
start_time = Time.now
|
|
25
26
|
|
|
26
27
|
puts "Stress testing: #{@spec_location}"
|
|
27
|
-
puts " Iterations: #{@iterations},
|
|
28
|
+
puts " Iterations: #{@iterations}, Seeds: #{seed_description(seeds)}, CI simulation: #{@ci_simulate}"
|
|
28
29
|
puts ""
|
|
29
30
|
|
|
30
31
|
@iterations.times do |i|
|
|
@@ -34,7 +35,7 @@ module Flaky
|
|
|
34
35
|
break
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
run_seed =
|
|
38
|
+
run_seed = seeds ? seeds[i % seeds.length] : rand(100_000)
|
|
38
39
|
cmd = "bundle exec rspec #{@spec_location} --seed #{run_seed} --format progress 2>&1"
|
|
39
40
|
|
|
40
41
|
output = nil
|
|
@@ -68,14 +69,53 @@ module Flaky
|
|
|
68
69
|
end
|
|
69
70
|
|
|
70
71
|
@repo.insert_stress_run(
|
|
71
|
-
spec_location: @spec_location, seed: @seed
|
|
72
|
-
passes: passes, failures: failures,
|
|
72
|
+
spec_location: @spec_location, seed: @seed.is_a?(Integer) ? @seed : nil,
|
|
73
|
+
iterations: @iterations, passes: passes, failures: failures,
|
|
74
|
+
ci_simulation: @ci_simulate ? 1 : 0
|
|
73
75
|
)
|
|
74
76
|
|
|
75
77
|
exit(failures > 0 ? 1 : 0)
|
|
76
78
|
ensure
|
|
77
79
|
@repo.close
|
|
78
80
|
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def resolve_seeds
|
|
85
|
+
case @seed
|
|
86
|
+
when :random
|
|
87
|
+
nil
|
|
88
|
+
when Integer
|
|
89
|
+
[@seed]
|
|
90
|
+
when nil
|
|
91
|
+
file, line = parse_location(@spec_location)
|
|
92
|
+
db_seeds = @repo.failing_seeds(file: file, line: line)
|
|
93
|
+
if db_seeds.any?
|
|
94
|
+
puts "Found #{db_seeds.length} known failing seed(s): #{db_seeds.join(', ')}"
|
|
95
|
+
db_seeds
|
|
96
|
+
else
|
|
97
|
+
puts "No known failing seeds — using random."
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def seed_description(seeds)
|
|
104
|
+
case @seed
|
|
105
|
+
when :random then "random"
|
|
106
|
+
when Integer then @seed.to_s
|
|
107
|
+
else seeds ? "#{seeds.length} from database" : "random"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def parse_location(loc)
|
|
112
|
+
if loc.include?(":")
|
|
113
|
+
parts = loc.rpartition(":")
|
|
114
|
+
[parts[0], parts[2].to_i]
|
|
115
|
+
else
|
|
116
|
+
[loc, nil]
|
|
117
|
+
end
|
|
118
|
+
end
|
|
79
119
|
end
|
|
80
120
|
end
|
|
81
121
|
end
|
data/lib/flaky/database.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "sqlite3"
|
|
|
4
4
|
|
|
5
5
|
module Flaky
|
|
6
6
|
class Database
|
|
7
|
-
SCHEMA_VERSION =
|
|
7
|
+
SCHEMA_VERSION = 2
|
|
8
8
|
|
|
9
9
|
def initialize(path = nil)
|
|
10
10
|
@path = path || Flaky.configuration.resolved_db_path
|
|
@@ -87,9 +87,14 @@ module Flaky
|
|
|
87
87
|
CREATE INDEX IF NOT EXISTS idx_ci_runs_branch ON ci_runs(branch);
|
|
88
88
|
CREATE INDEX IF NOT EXISTS idx_job_results_workflow ON job_results(workflow_id);
|
|
89
89
|
|
|
90
|
-
PRAGMA user_version =
|
|
90
|
+
PRAGMA user_version = 2;
|
|
91
91
|
SQL
|
|
92
92
|
end
|
|
93
|
+
|
|
94
|
+
if version < 2
|
|
95
|
+
db.execute("ALTER TABLE ci_runs ADD COLUMN commit_sha TEXT")
|
|
96
|
+
db.execute("PRAGMA user_version = 2")
|
|
97
|
+
end
|
|
93
98
|
end
|
|
94
99
|
end
|
|
95
100
|
end
|
data/lib/flaky/providers/base.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Flaky
|
|
|
10
10
|
def fetch_workflows(age: "24h")
|
|
11
11
|
cutoff = Time.now - AgeParser.to_seconds(age)
|
|
12
12
|
|
|
13
|
-
output = run_cmd("gh run list --branch #{config.branch} --limit 100 --json databaseId,conclusion,createdAt,headBranch,workflowName")
|
|
13
|
+
output = run_cmd("gh run list --branch #{config.branch} --limit 100 --json databaseId,conclusion,createdAt,headBranch,headSha,workflowName")
|
|
14
14
|
runs = JSON.parse(output)
|
|
15
15
|
|
|
16
16
|
runs.filter_map do |run|
|
|
@@ -21,6 +21,7 @@ module Flaky
|
|
|
21
21
|
id: run["databaseId"].to_s,
|
|
22
22
|
pipeline_id: run["databaseId"].to_s,
|
|
23
23
|
branch: run["headBranch"],
|
|
24
|
+
commit_sha: run["headSha"],
|
|
24
25
|
result: map_conclusion(run["conclusion"]),
|
|
25
26
|
created_at: run["createdAt"]
|
|
26
27
|
}
|
data/lib/flaky/repository.rb
CHANGED
|
@@ -18,10 +18,10 @@ module Flaky
|
|
|
18
18
|
!!connection.get_first_value("SELECT 1 FROM ci_runs WHERE workflow_id = ?", workflow_id)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def insert_ci_run(workflow_id:, pipeline_id:, branch:, result:, created_at:)
|
|
21
|
+
def insert_ci_run(workflow_id:, pipeline_id:, branch:, result:, created_at:, commit_sha: nil)
|
|
22
22
|
connection.execute(
|
|
23
|
-
"INSERT INTO ci_runs (workflow_id, pipeline_id, branch, result, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
24
|
-
[workflow_id, pipeline_id, branch, result, created_at]
|
|
23
|
+
"INSERT INTO ci_runs (workflow_id, pipeline_id, branch, result, created_at, commit_sha) VALUES (?, ?, ?, ?, ?, ?)",
|
|
24
|
+
[workflow_id, pipeline_id, branch, result, created_at, commit_sha]
|
|
25
25
|
)
|
|
26
26
|
end
|
|
27
27
|
|
|
@@ -53,7 +53,8 @@ module Flaky
|
|
|
53
53
|
tf.description,
|
|
54
54
|
COUNT(*) as failure_count,
|
|
55
55
|
MAX(tf.failed_at) as last_failure,
|
|
56
|
-
GROUP_CONCAT(DISTINCT tf.seed) as seeds
|
|
56
|
+
GROUP_CONCAT(DISTINCT tf.seed) as seeds,
|
|
57
|
+
GROUP_CONCAT(DISTINCT cr.commit_sha) as commit_shas
|
|
57
58
|
FROM test_failures tf
|
|
58
59
|
JOIN ci_runs cr ON cr.workflow_id = tf.workflow_id
|
|
59
60
|
WHERE cr.branch = ?
|
|
@@ -91,7 +92,8 @@ module Flaky
|
|
|
91
92
|
tf.job_name,
|
|
92
93
|
tf.branch,
|
|
93
94
|
tf.failed_at,
|
|
94
|
-
cr.workflow_id
|
|
95
|
+
cr.workflow_id,
|
|
96
|
+
cr.commit_sha
|
|
95
97
|
FROM test_failures tf
|
|
96
98
|
JOIN ci_runs cr ON cr.workflow_id = tf.workflow_id
|
|
97
99
|
WHERE #{conditions.join(" AND ")}
|
|
@@ -145,6 +147,23 @@ module Flaky
|
|
|
145
147
|
connection.execute("SELECT * FROM stress_runs ORDER BY created_at DESC LIMIT ?", limit)
|
|
146
148
|
end
|
|
147
149
|
|
|
150
|
+
# --- Seeds ---
|
|
151
|
+
|
|
152
|
+
def failing_seeds(file:, line: nil)
|
|
153
|
+
conditions = ["spec_file LIKE ?"]
|
|
154
|
+
params = ["%#{file}%"]
|
|
155
|
+
|
|
156
|
+
if line
|
|
157
|
+
conditions << "line_number = ?"
|
|
158
|
+
params << line
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
connection.execute(
|
|
162
|
+
"SELECT DISTINCT seed FROM test_failures WHERE #{conditions.join(' AND ')} ORDER BY failed_at DESC",
|
|
163
|
+
params
|
|
164
|
+
).map { |row| row["seed"] }
|
|
165
|
+
end
|
|
166
|
+
|
|
148
167
|
# --- Stress ---
|
|
149
168
|
|
|
150
169
|
def insert_stress_run(spec_location:, seed:, iterations:, passes:, failures:, ci_simulation:)
|
data/lib/flaky/tasks/flaky.rake
CHANGED
|
@@ -36,7 +36,7 @@ FLAKY_HELP = <<~HELP
|
|
|
36
36
|
SINCE Number of days to look back for rank [default: 30]
|
|
37
37
|
SPEC Spec file location (path/to/spec.rb or path:line)
|
|
38
38
|
N Number of stress test iterations [default: 20]
|
|
39
|
-
SEED RSpec seed
|
|
39
|
+
SEED RSpec seed (omit=use known failing seeds, "random"=random)
|
|
40
40
|
CI Set to "true" to enable CI simulation (latency + reduced threads)
|
|
41
41
|
TIMEOUT Stress test timeout in seconds [default: 600]
|
|
42
42
|
|
|
@@ -78,10 +78,15 @@ namespace :flaky do
|
|
|
78
78
|
task stress: :environment do
|
|
79
79
|
require "flaky/commands/stress"
|
|
80
80
|
spec = ENV["SPEC"] || abort("Usage: bin/rails flaky:stress SPEC=path/to/spec.rb:42")
|
|
81
|
+
seed = case ENV["SEED"]
|
|
82
|
+
when nil then nil
|
|
83
|
+
when "random" then :random
|
|
84
|
+
else ENV["SEED"].to_i
|
|
85
|
+
end
|
|
81
86
|
Flaky::Commands::Stress.new(
|
|
82
87
|
spec_location: spec,
|
|
83
88
|
iterations: ENV.fetch("N", "20").to_i,
|
|
84
|
-
seed:
|
|
89
|
+
seed: seed,
|
|
85
90
|
ci_simulate: ENV["CI"] == "true",
|
|
86
91
|
timeout: ENV.fetch("TIMEOUT", "600").to_i
|
|
87
92
|
).execute
|
data/lib/flaky/version.rb
CHANGED