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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a9f46407de5b2f8ef6fa5178ec220bc84fef9376e5bc68e7d3703066dd663b7
4
- data.tar.gz: ee59c6d55778658f109cdefe410d770af048579f1381fab466aae6c19d6b4392
3
+ metadata.gz: 6216aed376755cc4ce188f8c5dc42b0146d1c7b3397c409c3eef9238eb8b987e
4
+ data.tar.gz: a41b69d5707e4fcb93ebb7bc4bfbefda33ce827c49475a1e8a5d0b85506cba18
5
5
  SHA512:
6
- metadata.gz: 90cdea90b46b2aad131864b0caa56e35243fe97c346242bdb8462873d8fab3df64d91270a234219ff9a74a0cf40860daa09773e3da14a5b81dbb7ef437f3a442
7
- data.tar.gz: 421d03e4fac9a19c847d428a8186f91730622f376fece021414617a4121b30482ca86da05277ddfff443732de05c55432c0d1ea41e077f01797308f2e98750c0
6
+ metadata.gz: 7e1fd50da98dfafcf952f79c39b920e7b25e4d381c5f8e3a3afa5dd71cf2eb239a59933163abb7102345aa740aa2ec434b0e94d9e764ab233401cc389bea3ccf
7
+ data.tar.gz: 1b1f57e521913ff056e5c1732eccf76b5fe41b5ae66c25a830330fc01a2e7ad10b2a47d49d79ac211557ae634d86ca7305b67ff068474e34587d99f95d5c0a5e
@@ -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 "-" * 100
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
- puts format("%-20s %-8d %-30s %s", row["failed_at"], row["seed"], row["job_name"], row["workflow_id"])
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}"
@@ -36,6 +36,7 @@ module Flaky
36
36
  puts "\n \e[33m▶ Next to investigate:\e[0m #{location}"
37
37
  puts " #{row['description']}"
38
38
  puts " Seeds: #{row['seeds']}"
39
+ puts " Commits: #{row['commit_shas']}" if row["commit_shas"]
39
40
  puts ""
40
41
  end
41
42
  end
@@ -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}, Seed: #{@seed || 'random'}, CI simulation: #{@ci_simulate}"
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 = @seed || rand(100_000)
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, iterations: @iterations,
72
- passes: passes, failures: failures, ci_simulation: @ci_simulate ? 1 : 0
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
@@ -4,7 +4,7 @@ require "sqlite3"
4
4
 
5
5
  module Flaky
6
6
  class Database
7
- SCHEMA_VERSION = 1
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 = 1;
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
@@ -10,7 +10,7 @@ module Flaky
10
10
  end
11
11
 
12
12
  # Returns Array of Hashes:
13
- # { id:, pipeline_id:, branch:, created_at: }
13
+ # { id:, pipeline_id:, branch:, commit_sha:, created_at: }
14
14
  def fetch_workflows(age: "24h")
15
15
  raise NotImplementedError
16
16
  end
@@ -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
  }
@@ -33,6 +33,7 @@ module Flaky
33
33
  id: wf["wf_id"],
34
34
  pipeline_id: wf["initial_ppl_id"],
35
35
  branch: wf["branch_name"],
36
+ commit_sha: wf["commit_sha"],
36
37
  created_at: created_at.strftime("%Y-%m-%d %H:%M:%S")
37
38
  }
38
39
  end
@@ -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:)
@@ -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 for deterministic ordering
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: ENV["SEED"]&.to_i,
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flaky
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flaky-friend
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Flytedesk