rspec-flake-classifier 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +301 -0
- data/Rakefile +8 -0
- data/exe/rspec-flake +6 -0
- data/lib/rspec/flake/classifier/classify/classifier.rb +228 -0
- data/lib/rspec/flake/classifier/classify/context.rb +41 -0
- data/lib/rspec/flake/classifier/classify/result.rb +44 -0
- data/lib/rspec/flake/classifier/cli.rb +298 -0
- data/lib/rspec/flake/classifier/configuration.rb +40 -0
- data/lib/rspec/flake/classifier/coverage_snapshot.rb +89 -0
- data/lib/rspec/flake/classifier/deflaker.rb +102 -0
- data/lib/rspec/flake/classifier/evaluation.rb +127 -0
- data/lib/rspec/flake/classifier/example_history.rb +24 -0
- data/lib/rspec/flake/classifier/features.rb +42 -0
- data/lib/rspec/flake/classifier/formatter.rb +194 -0
- data/lib/rspec/flake/classifier/integrations.rb +247 -0
- data/lib/rspec/flake/classifier/predictor.rb +144 -0
- data/lib/rspec/flake/classifier/probe_evidence.rb +77 -0
- data/lib/rspec/flake/classifier/rerun/bisect_dependency_search.rb +81 -0
- data/lib/rspec/flake/classifier/rerun/isolated_runner.rb +69 -0
- data/lib/rspec/flake/classifier/rerun/protocol.rb +83 -0
- data/lib/rspec/flake/classifier/rerun/result.rb +82 -0
- data/lib/rspec/flake/classifier/runtime_controls.rb +63 -0
- data/lib/rspec/flake/classifier/sensitivity.rb +82 -0
- data/lib/rspec/flake/classifier/signature.rb +59 -0
- data/lib/rspec/flake/classifier/store/jsonl_store.rb +131 -0
- data/lib/rspec/flake/classifier/version.rb +13 -0
- data/lib/rspec/flake/classifier.rb +285 -0
- data/sig/rspec/flake/classifier.rbs +176 -0
- metadata +135 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "classifier/classify/classifier"
|
|
4
|
+
require_relative "classifier/configuration"
|
|
5
|
+
require_relative "classifier/coverage_snapshot"
|
|
6
|
+
require_relative "classifier/deflaker"
|
|
7
|
+
require_relative "classifier/example_history"
|
|
8
|
+
require_relative "classifier/evaluation"
|
|
9
|
+
require_relative "classifier/features"
|
|
10
|
+
require_relative "classifier/integrations"
|
|
11
|
+
require_relative "classifier/predictor"
|
|
12
|
+
require_relative "classifier/probe_evidence"
|
|
13
|
+
require_relative "classifier/rerun/isolated_runner"
|
|
14
|
+
require_relative "classifier/rerun/protocol"
|
|
15
|
+
require_relative "classifier/runtime_controls"
|
|
16
|
+
require_relative "classifier/sensitivity"
|
|
17
|
+
require_relative "classifier/signature"
|
|
18
|
+
require_relative "classifier/store/jsonl_store"
|
|
19
|
+
require_relative "classifier/version"
|
|
20
|
+
|
|
21
|
+
module RSpec
|
|
22
|
+
module FlakeClassifier
|
|
23
|
+
class Error < StandardError; end
|
|
24
|
+
|
|
25
|
+
RuntimeControls.activate!
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def configuration
|
|
29
|
+
@configuration ||= Configuration.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def configure(rspec_config = nil)
|
|
33
|
+
yield(configuration) if block_given?
|
|
34
|
+
RSpecIntegration.install(rspec_config, configuration) if rspec_config
|
|
35
|
+
configuration
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def signature_for(exception)
|
|
39
|
+
Signature.from_exception(exception)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def store
|
|
43
|
+
Store::JSONLStore.new(configuration.store_path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def classify(input = nil, **attributes)
|
|
47
|
+
Classify::Classifier.new.classify(input, **attributes)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def investigate(example_id, seed: nil, prior_examples: [], runner: nil)
|
|
51
|
+
runner ||= Rerun::IsolatedRunner.new(command: configuration.rspec_command)
|
|
52
|
+
Rerun::Protocol.new(
|
|
53
|
+
runner: runner,
|
|
54
|
+
same_order_runs: configuration.auto_rerun_attempts
|
|
55
|
+
).investigate(example_id, seed: seed, prior_examples: prior_examples)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def sensitivity(example_id, factors: configuration.sensitivity_factors, seed: nil, runner: nil)
|
|
59
|
+
runner ||= Rerun::IsolatedRunner.new(command: configuration.rspec_command)
|
|
60
|
+
Sensitivity.new(runner: runner).analyze(example_id, factors: factors, seed: seed)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
module RSpecIntegration
|
|
65
|
+
module_function
|
|
66
|
+
|
|
67
|
+
def install(rspec_config, configuration)
|
|
68
|
+
return if ENV[Rerun::IsolatedRunner::DISABLE_ENV] == "1"
|
|
69
|
+
return if rspec_config.instance_variable_defined?(:@flake_classifier_installed)
|
|
70
|
+
|
|
71
|
+
rspec_config.instance_variable_set(:@flake_classifier_installed, true)
|
|
72
|
+
reset!
|
|
73
|
+
install_around_hook(rspec_config) do |example|
|
|
74
|
+
prior_examples = RSpecIntegration.prior_examples
|
|
75
|
+
coverage_before = CoverageSnapshot.capture
|
|
76
|
+
example.run
|
|
77
|
+
coverage = RSpecIntegration.coverage_for(example, configuration, coverage_before)
|
|
78
|
+
if example.exception
|
|
79
|
+
RSpecIntegration.handle_failure(
|
|
80
|
+
example,
|
|
81
|
+
configuration,
|
|
82
|
+
prior_examples: prior_examples,
|
|
83
|
+
coverage: coverage
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
ensure
|
|
87
|
+
RSpecIntegration.record_example(example)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def install_around_hook(rspec_config, &hook)
|
|
92
|
+
if rspec_config.respond_to?(:hooks) && rspec_config.hooks.respond_to?(:register)
|
|
93
|
+
rspec_config.hooks.register(:append, :around, :each, &hook)
|
|
94
|
+
else
|
|
95
|
+
rspec_config.around(:each, &hook)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def handle_failure(example, configuration, prior_examples: [], coverage: {})
|
|
100
|
+
signature = Signature.from_exception(example.exception)
|
|
101
|
+
store = Store::JSONLStore.new(configuration.store_path)
|
|
102
|
+
known_flake = store.known_flake?(signature.digest)
|
|
103
|
+
probes = ProbeEvidence.new(provider: configuration.probe_provider).collect(example)
|
|
104
|
+
deflaker = deflaker_result(configuration, coverage)
|
|
105
|
+
sensitivity = nil
|
|
106
|
+
metadata = failure_metadata(example, prior_examples, coverage, probes, deflaker, sensitivity)
|
|
107
|
+
|
|
108
|
+
if known_flake && configuration.skip_known_flakes
|
|
109
|
+
store.record(signature, classification: known_classification, metadata: metadata)
|
|
110
|
+
clear_exception(example)
|
|
111
|
+
return
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
sensitivity = sensitivity_result(example, configuration)
|
|
115
|
+
metadata = failure_metadata(example, prior_examples, coverage, probes, deflaker, sensitivity)
|
|
116
|
+
|
|
117
|
+
investigation = nil
|
|
118
|
+
if configuration.auto_rerun
|
|
119
|
+
investigation = rerun_example(example, configuration, prior_examples)
|
|
120
|
+
clear_exception(example) if investigation.flaky?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
classification = classify_failure(
|
|
124
|
+
example,
|
|
125
|
+
investigation,
|
|
126
|
+
probes: probes,
|
|
127
|
+
deflaker: deflaker,
|
|
128
|
+
sensitivity: sensitivity
|
|
129
|
+
)
|
|
130
|
+
Integrations.tag_ci_execution(example: example, signature: signature, classification: classification)
|
|
131
|
+
store.record(signature, classification: classification, metadata: metadata)
|
|
132
|
+
example.metadata[:flake_classifier] = {
|
|
133
|
+
signature: signature.digest,
|
|
134
|
+
known_flake: known_flake,
|
|
135
|
+
coverage: coverage,
|
|
136
|
+
deflaker: deflaker&.to_h,
|
|
137
|
+
sensitivity: sensitivity&.to_h,
|
|
138
|
+
probes: probes,
|
|
139
|
+
investigation: investigation&.to_h,
|
|
140
|
+
classification: classification.to_h
|
|
141
|
+
}
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def rerun_example(example, configuration, prior_examples)
|
|
145
|
+
RSpec::FlakeClassifier.investigate(
|
|
146
|
+
example.id,
|
|
147
|
+
seed: current_seed,
|
|
148
|
+
prior_examples: prior_examples,
|
|
149
|
+
runner: Rerun::IsolatedRunner.new(command: configuration.rspec_command)
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def classify_failure(example, investigation, probes:, deflaker:, sensitivity:)
|
|
154
|
+
Classify::Classifier.new.classify(
|
|
155
|
+
message: example.exception.message,
|
|
156
|
+
backtrace: example.exception.backtrace,
|
|
157
|
+
order_dependent: investigation&.order_type == "od",
|
|
158
|
+
duration_samples: investigation&.runs&.map(&:duration),
|
|
159
|
+
resources: probes.fetch(:resources, []),
|
|
160
|
+
files: probes.fetch(:files, []),
|
|
161
|
+
sockets: probes.fetch(:sockets, []),
|
|
162
|
+
source: probes[:source],
|
|
163
|
+
metadata: {
|
|
164
|
+
deflaker: deflaker&.to_h,
|
|
165
|
+
sensitivity: sensitivity&.to_h
|
|
166
|
+
}.compact
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def coverage_for(example, configuration, coverage_before)
|
|
171
|
+
provided = provider_value(configuration.coverage_provider, example)
|
|
172
|
+
return provided if provided
|
|
173
|
+
|
|
174
|
+
integration_coverage = Integrations.coverage_for(example)
|
|
175
|
+
return integration_coverage if integration_coverage
|
|
176
|
+
|
|
177
|
+
metadata_coverage = example.metadata[:flake_classifier_coverage] || example.metadata[:covered_lines]
|
|
178
|
+
return metadata_coverage if metadata_coverage
|
|
179
|
+
|
|
180
|
+
coverage_before.delta
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def deflaker_result(configuration, coverage)
|
|
184
|
+
return nil unless configuration.deflaker
|
|
185
|
+
return nil if coverage.nil? || coverage.empty?
|
|
186
|
+
|
|
187
|
+
Deflaker.new.suspect?(
|
|
188
|
+
coverage: coverage,
|
|
189
|
+
changed_lines: changed_lines(configuration),
|
|
190
|
+
base: deflaker_base(configuration)
|
|
191
|
+
)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def sensitivity_result(example, configuration)
|
|
195
|
+
return nil unless configuration.run_sensitivity
|
|
196
|
+
|
|
197
|
+
RSpec::FlakeClassifier.sensitivity(
|
|
198
|
+
example.id,
|
|
199
|
+
factors: configuration.sensitivity_factors,
|
|
200
|
+
seed: current_seed,
|
|
201
|
+
runner: Rerun::IsolatedRunner.new(command: configuration.rspec_command)
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def changed_lines(configuration)
|
|
206
|
+
provider_value(configuration.changed_lines_provider)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def deflaker_base(configuration)
|
|
210
|
+
return "HEAD" unless configuration.deflaker.is_a?(Hash)
|
|
211
|
+
|
|
212
|
+
configuration.deflaker.fetch(:base, configuration.deflaker.fetch("base", "HEAD"))
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def provider_value(provider, *args)
|
|
216
|
+
return nil unless provider
|
|
217
|
+
|
|
218
|
+
return provider.call if provider.respond_to?(:arity) && provider.arity.zero?
|
|
219
|
+
|
|
220
|
+
provider.call(*args)
|
|
221
|
+
rescue StandardError
|
|
222
|
+
nil
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def failure_metadata(example, prior_examples, coverage, probes, deflaker, sensitivity)
|
|
226
|
+
{
|
|
227
|
+
example_id: example.id,
|
|
228
|
+
file_path: example.metadata[:file_path],
|
|
229
|
+
line_number: example.metadata[:line_number],
|
|
230
|
+
prior_examples: prior_examples,
|
|
231
|
+
coverage: coverage,
|
|
232
|
+
probes: probes,
|
|
233
|
+
deflaker: deflaker&.to_h,
|
|
234
|
+
sensitivity: sensitivity&.to_h
|
|
235
|
+
}.compact
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def known_classification
|
|
239
|
+
Classify::Result.new(
|
|
240
|
+
status: "flaky",
|
|
241
|
+
labels: [
|
|
242
|
+
Classify::Label.new(
|
|
243
|
+
category: "known_flaky",
|
|
244
|
+
confidence: 1.0,
|
|
245
|
+
evidence: ["signature matched the flake store"],
|
|
246
|
+
action: "Inspect the stored classification and linked commits."
|
|
247
|
+
)
|
|
248
|
+
]
|
|
249
|
+
)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def clear_exception(example)
|
|
253
|
+
example.instance_variable_set(:@exception, nil)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def prior_examples
|
|
257
|
+
history.prior_examples
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def record_example(example)
|
|
261
|
+
history.record(example)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def reset!
|
|
265
|
+
@history = ExampleHistory.new
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def history
|
|
269
|
+
@history ||= ExampleHistory.new
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def current_seed
|
|
273
|
+
return unless defined?(::RSpec) && ::RSpec.respond_to?(:configuration)
|
|
274
|
+
|
|
275
|
+
::RSpec.configuration.seed
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
module Rspec
|
|
282
|
+
module Flake
|
|
283
|
+
Classifier = ::RSpec::FlakeClassifier unless const_defined?(:Classifier, false)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module FlakeClassifier
|
|
3
|
+
VERSION: String
|
|
4
|
+
|
|
5
|
+
def self.configuration: () -> Configuration
|
|
6
|
+
def self.configure: (?untyped rspec_config) { (Configuration) -> void } -> Configuration
|
|
7
|
+
def self.signature_for: (Exception exception) -> Signature
|
|
8
|
+
def self.store: () -> Store::JSONLStore
|
|
9
|
+
def self.classify: (?untyped input, **untyped attributes) -> Classify::Result
|
|
10
|
+
def self.investigate: (String example_id, ?seed: Integer?, ?prior_examples: Array[String], ?runner: untyped) -> Rerun::Investigation
|
|
11
|
+
def self.sensitivity: (String example_id, ?factors: Array[Symbol], ?seed: Integer?, ?runner: untyped) -> Sensitivity::Result
|
|
12
|
+
|
|
13
|
+
class Configuration
|
|
14
|
+
attr_accessor store: String
|
|
15
|
+
attr_accessor auto_rerun: bool | Hash[Symbol, untyped]
|
|
16
|
+
attr_accessor deflaker: bool
|
|
17
|
+
attr_accessor rspec_command: Array[String]?
|
|
18
|
+
attr_accessor same_order_runs: Integer
|
|
19
|
+
attr_accessor skip_known_flakes: bool
|
|
20
|
+
attr_accessor coverage_provider: untyped
|
|
21
|
+
attr_accessor changed_lines_provider: untyped
|
|
22
|
+
attr_accessor probe_provider: untyped
|
|
23
|
+
attr_accessor run_sensitivity: bool
|
|
24
|
+
attr_accessor sensitivity_factors: Array[Symbol]
|
|
25
|
+
|
|
26
|
+
def initialize: () -> void
|
|
27
|
+
def auto_rerun_attempts: () -> Integer
|
|
28
|
+
def store_path: () -> String
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Signature
|
|
32
|
+
attr_reader message: String
|
|
33
|
+
attr_reader backtrace: Array[String]
|
|
34
|
+
attr_reader normalized: String
|
|
35
|
+
attr_reader digest: String
|
|
36
|
+
|
|
37
|
+
def self.from_exception: (Exception exception) -> Signature
|
|
38
|
+
def initialize: (message: String, ?backtrace: Array[String]) -> void
|
|
39
|
+
def to_h: () -> Hash[String, untyped]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Deflaker
|
|
43
|
+
def suspect?: (coverage: untyped, ?changed_lines: untyped, ?base: String) -> Deflaker::Result
|
|
44
|
+
def git_changed_lines: (?String base) -> Hash[String, Array[Integer]]
|
|
45
|
+
def parse_diff: (String diff) -> Hash[String, Array[Integer]]
|
|
46
|
+
|
|
47
|
+
Result: singleton(::Struct)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class Sensitivity
|
|
51
|
+
def initialize: (runner: untyped) -> void
|
|
52
|
+
def analyze: (String example_id, ?factors: Array[Symbol], ?seed: Integer?) -> Sensitivity::Result
|
|
53
|
+
|
|
54
|
+
Result: singleton(::Struct)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class CoverageSnapshot
|
|
58
|
+
def self.capture: () -> CoverageSnapshot
|
|
59
|
+
def self.null: () -> CoverageSnapshot
|
|
60
|
+
def initialize: (Hash[String, untyped] data) -> void
|
|
61
|
+
def delta: () -> Hash[String, Array[Integer]]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class ProbeEvidence
|
|
65
|
+
def initialize: (?provider: untyped) -> void
|
|
66
|
+
def collect: (untyped example) -> Hash[Symbol, untyped]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class ExampleHistory
|
|
70
|
+
def initialize: () -> void
|
|
71
|
+
def prior_examples: () -> Array[String]
|
|
72
|
+
def record: (untyped example) -> void
|
|
73
|
+
def clear: () -> void
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class Features
|
|
77
|
+
def extract: (?file: String?, ?source: String?, ?duration: Float?, ?metadata: Hash[Symbol, untyped]) -> Hash[String, untyped]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class Predictor
|
|
81
|
+
attr_reader weights: Hash[String, Float]
|
|
82
|
+
|
|
83
|
+
def self.train: (Array[Hash[String | Symbol, untyped]] examples) -> Predictor
|
|
84
|
+
def self.load: (String path) -> Predictor
|
|
85
|
+
def initialize: (?weights: Hash[String | Symbol, untyped]) -> void
|
|
86
|
+
def score: (Hash[String | Symbol, untyped] features) -> Float
|
|
87
|
+
def rank: (Array[Hash[String | Symbol, untyped]] feature_sets) -> Array[Hash[String, untyped]]
|
|
88
|
+
def to_h: () -> Hash[String, Hash[String, Float]]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class Evaluation
|
|
92
|
+
def classification: (predictions: Array[Hash[String | Symbol, untyped]], ground_truth: Array[Hash[String | Symbol, untyped]]) -> Hash[String, untyped]
|
|
93
|
+
def deflaker: (records: Array[Hash[String | Symbol, untyped]]) -> Hash[String, untyped]
|
|
94
|
+
def signatures: (records: Array[Hash[String | Symbol, untyped]]) -> Hash[String, untyped]
|
|
95
|
+
def idflakies: (records: Array[Hash[String | Symbol, untyped]]) -> Hash[String, untyped]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
module Integrations
|
|
99
|
+
def self.coverage_for: (untyped example) -> untyped
|
|
100
|
+
def self.probe_for: (untyped example) -> untyped
|
|
101
|
+
def self.tag_ci_execution: (example: untyped, signature: Signature, classification: untyped) -> void
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class Formatter
|
|
105
|
+
def initialize: (untyped output) -> void
|
|
106
|
+
def example_failed: (untyped notification) -> void
|
|
107
|
+
def dump_summary: (untyped notification) -> void
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class JUnitFormatter
|
|
111
|
+
def initialize: (untyped output) -> void
|
|
112
|
+
def example_passed: (untyped notification) -> void
|
|
113
|
+
def example_failed: (untyped notification) -> void
|
|
114
|
+
def dump_summary: (untyped notification) -> void
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
module Store
|
|
118
|
+
class JSONLStore
|
|
119
|
+
attr_reader path: String
|
|
120
|
+
|
|
121
|
+
def initialize: (String path) -> void
|
|
122
|
+
def record: (Signature signature, ?classification: untyped, ?metadata: Hash[Symbol, untyped]) -> Hash[String, untyped]
|
|
123
|
+
def update_classification: (String digest, classification: untyped, ?metadata: Hash[Symbol, untyped]) -> Hash[String, untyped]?
|
|
124
|
+
def find: (String digest) -> Hash[String, untyped]?
|
|
125
|
+
def known_flake?: (String digest) -> bool
|
|
126
|
+
def all: () -> Hash[String, Hash[String, untyped]]
|
|
127
|
+
def entries: () -> Array[Hash[String, untyped]]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
module Classify
|
|
132
|
+
Label: singleton(::Struct)
|
|
133
|
+
|
|
134
|
+
class Result
|
|
135
|
+
attr_reader status: String
|
|
136
|
+
attr_reader labels: Array[untyped]
|
|
137
|
+
attr_reader evidence: Array[String]
|
|
138
|
+
|
|
139
|
+
def initialize: (status: String, labels: Array[untyped], ?evidence: Array[String]) -> void
|
|
140
|
+
def primary_label: () -> untyped
|
|
141
|
+
def flaky?: () -> bool
|
|
142
|
+
def to_h: () -> Hash[String, untyped]
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
module Rerun
|
|
147
|
+
Result: singleton(::Struct)
|
|
148
|
+
|
|
149
|
+
class BisectDependencySearch
|
|
150
|
+
def initialize: (runner: untyped) -> void
|
|
151
|
+
def find: (String example_id, Array[String] prior_examples, ?seed: Integer?) -> Array[String]?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class Investigation
|
|
155
|
+
attr_reader example_id: String
|
|
156
|
+
attr_reader status: String
|
|
157
|
+
attr_reader order_type: String?
|
|
158
|
+
attr_reader runs: Array[untyped]
|
|
159
|
+
attr_reader dependency_examples: Array[String]
|
|
160
|
+
attr_reader dependency_role: String?
|
|
161
|
+
|
|
162
|
+
def flaky?: () -> bool
|
|
163
|
+
def true_failure?: () -> bool
|
|
164
|
+
def labels: () -> Array[String]
|
|
165
|
+
def to_h: () -> Hash[String, untyped]
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
module Rspec
|
|
172
|
+
module Flake
|
|
173
|
+
module Classifier
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rspec-flake-classifier
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yudai Takada
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rspec-core
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.10'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '4.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '3.10'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '4.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: rspec-covers
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: 0.1.0
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '1.0'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: 0.1.0
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '1.0'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: rspec-hermetic
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 0.1.0
|
|
59
|
+
- - "<"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 0.1.0
|
|
69
|
+
- - "<"
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '1.0'
|
|
72
|
+
description: RSpec flake classifier records normalized failure signatures, reruns
|
|
73
|
+
failing examples in isolated processes, and assigns flaky-test cause labels.
|
|
74
|
+
email:
|
|
75
|
+
- t.yudai92@gmail.com
|
|
76
|
+
executables:
|
|
77
|
+
- rspec-flake
|
|
78
|
+
extensions: []
|
|
79
|
+
extra_rdoc_files: []
|
|
80
|
+
files:
|
|
81
|
+
- LICENSE.txt
|
|
82
|
+
- README.md
|
|
83
|
+
- Rakefile
|
|
84
|
+
- exe/rspec-flake
|
|
85
|
+
- lib/rspec/flake/classifier.rb
|
|
86
|
+
- lib/rspec/flake/classifier/classify/classifier.rb
|
|
87
|
+
- lib/rspec/flake/classifier/classify/context.rb
|
|
88
|
+
- lib/rspec/flake/classifier/classify/result.rb
|
|
89
|
+
- lib/rspec/flake/classifier/cli.rb
|
|
90
|
+
- lib/rspec/flake/classifier/configuration.rb
|
|
91
|
+
- lib/rspec/flake/classifier/coverage_snapshot.rb
|
|
92
|
+
- lib/rspec/flake/classifier/deflaker.rb
|
|
93
|
+
- lib/rspec/flake/classifier/evaluation.rb
|
|
94
|
+
- lib/rspec/flake/classifier/example_history.rb
|
|
95
|
+
- lib/rspec/flake/classifier/features.rb
|
|
96
|
+
- lib/rspec/flake/classifier/formatter.rb
|
|
97
|
+
- lib/rspec/flake/classifier/integrations.rb
|
|
98
|
+
- lib/rspec/flake/classifier/predictor.rb
|
|
99
|
+
- lib/rspec/flake/classifier/probe_evidence.rb
|
|
100
|
+
- lib/rspec/flake/classifier/rerun/bisect_dependency_search.rb
|
|
101
|
+
- lib/rspec/flake/classifier/rerun/isolated_runner.rb
|
|
102
|
+
- lib/rspec/flake/classifier/rerun/protocol.rb
|
|
103
|
+
- lib/rspec/flake/classifier/rerun/result.rb
|
|
104
|
+
- lib/rspec/flake/classifier/runtime_controls.rb
|
|
105
|
+
- lib/rspec/flake/classifier/sensitivity.rb
|
|
106
|
+
- lib/rspec/flake/classifier/signature.rb
|
|
107
|
+
- lib/rspec/flake/classifier/store/jsonl_store.rb
|
|
108
|
+
- lib/rspec/flake/classifier/version.rb
|
|
109
|
+
- sig/rspec/flake/classifier.rbs
|
|
110
|
+
homepage: https://github.com/ydah/rspec-flake-classifier
|
|
111
|
+
licenses:
|
|
112
|
+
- MIT
|
|
113
|
+
metadata:
|
|
114
|
+
allowed_push_host: https://rubygems.org
|
|
115
|
+
homepage_uri: https://github.com/ydah/rspec-flake-classifier
|
|
116
|
+
source_code_uri: https://github.com/ydah/rspec-flake-classifier
|
|
117
|
+
rubygems_mfa_required: 'true'
|
|
118
|
+
rdoc_options: []
|
|
119
|
+
require_paths:
|
|
120
|
+
- lib
|
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
122
|
+
requirements:
|
|
123
|
+
- - ">="
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: 3.2.0
|
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '0'
|
|
131
|
+
requirements: []
|
|
132
|
+
rubygems_version: 4.0.6
|
|
133
|
+
specification_version: 4
|
|
134
|
+
summary: Detect, record, and classify flaky RSpec failures.
|
|
135
|
+
test_files: []
|