rspec-rewind 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.
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Rewind
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec/core'
4
+ require 'time'
5
+
6
+ require_relative 'rewind/version'
7
+ require_relative 'rewind/backoff'
8
+ require_relative 'rewind/retry_budget'
9
+ require_relative 'rewind/flaky_reporter'
10
+ require_relative 'rewind/matcher_validation'
11
+ require_relative 'rewind/configuration'
12
+ require_relative 'rewind/example_context'
13
+ require_relative 'rewind/event'
14
+ require_relative 'rewind/retry_count_resolver'
15
+ require_relative 'rewind/retry_delay_resolver'
16
+ require_relative 'rewind/retry_event_builder'
17
+ require_relative 'rewind/retry_notifier'
18
+ require_relative 'rewind/flaky_transition'
19
+ require_relative 'rewind/attempt_runner'
20
+ require_relative 'rewind/retry_transition'
21
+ require_relative 'rewind/retry_gate'
22
+ require_relative 'rewind/retry_loop'
23
+ require_relative 'rewind/runner_logger'
24
+ require_relative 'rewind/runner_components'
25
+ require_relative 'rewind/example_state_resetter'
26
+ require_relative 'rewind/retry_policy'
27
+ require_relative 'rewind/retry_decision'
28
+ require_relative 'rewind/runner'
29
+ require_relative 'rewind/example_methods'
30
+
31
+ module RSpec
32
+ module Rewind
33
+ class << self
34
+ def configuration
35
+ @configuration ||= Configuration.new
36
+ end
37
+
38
+ def configure
39
+ yield(configuration)
40
+ end
41
+
42
+ def reset_configuration!
43
+ @configuration = Configuration.new
44
+ end
45
+
46
+ def install!
47
+ return if @installed
48
+
49
+ ::RSpec::Core::Example.include(ExampleMethods)
50
+ ::RSpec::Core::Example::Procsy.include(ExampleMethods) if defined?(::RSpec::Core::Example::Procsy)
51
+
52
+ ::RSpec.configure do |config|
53
+ config.around(:each) do |example|
54
+ if example.metadata[:rewind] == false
55
+ example.run
56
+ else
57
+ example.run_with_rewind
58
+ end
59
+ end
60
+ end
61
+
62
+ @installed = true
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ RSpec::Rewind.install!
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rspec/rewind'
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/rspec/rewind/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'rspec-rewind'
7
+ spec.version = RSpec::Rewind::VERSION
8
+ spec.authors = ['Yudai Takada']
9
+ spec.email = ['t.yudai92@gmail.com']
10
+
11
+ spec.summary = 'Modern retry orchestration for flaky RSpec examples'
12
+ spec.description = <<~DESC
13
+ rspec-rewind provides deterministic retry policies, exception filters,
14
+ configurable backoff strategies, and flaky test observability for RSpec.
15
+ DESC
16
+ spec.homepage = 'https://github.com/ydah/rspec-rewind'
17
+ spec.license = 'MIT'
18
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.1')
19
+
20
+ spec.metadata = {
21
+ 'homepage_uri' => spec.homepage,
22
+ 'source_code_uri' => spec.homepage,
23
+ 'changelog_uri' => "#{spec.homepage}/blob/main/CHANGELOG.md",
24
+ 'rubygems_mfa_required' => 'true'
25
+ }
26
+
27
+ spec.files = Dir.chdir(__dir__) do
28
+ code_and_signatures = Dir.glob('{lib,sig}/**/*', File::FNM_DOTMATCH).reject do |path|
29
+ path.include?('/.') || File.directory?(path)
30
+ end
31
+
32
+ code_and_signatures + %w[CHANGELOG.md LICENSE.txt README.md rspec-rewind.gemspec]
33
+ end
34
+
35
+ spec.require_paths = ['lib']
36
+
37
+ spec.add_dependency 'rspec-core', '>= 3.12', '< 4.0'
38
+ end
@@ -0,0 +1,211 @@
1
+ module RSpec
2
+ module Rewind
3
+ VERSION: String
4
+ EVENT_SCHEMA_VERSION: Integer
5
+
6
+ class << self
7
+ def configuration: () -> Configuration
8
+ def configure: () { (Configuration) -> void } -> void
9
+ def reset_configuration!: () -> Configuration
10
+ def install!: () -> bool?
11
+ end
12
+
13
+ module Backoff
14
+ def self.fixed: (Numeric seconds) -> untyped
15
+ def self.linear: (step: Numeric, ?max: Numeric?) -> untyped
16
+ def self.exponential: (base: Numeric, ?factor: Numeric, ?max: Numeric?, ?jitter: Numeric) -> untyped
17
+ end
18
+
19
+ class Configuration
20
+ attr_reader default_retries: Integer
21
+ attr_reader backoff: Numeric | untyped
22
+ attr_reader retry_on: Array[untyped]
23
+ attr_reader skip_retry_on: Array[untyped]
24
+ attr_reader retry_if: untyped?
25
+ attr_reader retry_callback: untyped?
26
+ attr_reader flaky_callback: untyped?
27
+ attr_reader verbose: bool
28
+ attr_reader display_retry_failure_messages: bool
29
+ attr_reader clear_lets_on_failure: bool
30
+ attr_reader retry_budget: RetryBudget
31
+ attr_reader flaky_reporter: FlakyReporter::NullReporter | FlakyReporter::JsonlReporter | untyped
32
+
33
+ def initialize: () -> void
34
+ def default_retries=: (untyped) -> Integer
35
+ def backoff=: (untyped) -> Numeric | untyped
36
+ def retry_on=: (untyped) -> Array[untyped]
37
+ def skip_retry_on=: (untyped) -> Array[untyped]
38
+ def retry_if=: (untyped?) -> untyped?
39
+ def retry_callback=: (untyped?) -> untyped?
40
+ def flaky_callback=: (untyped?) -> untyped?
41
+ def verbose=: (bool) -> bool
42
+ def display_retry_failure_messages=: (bool) -> bool
43
+ def clear_lets_on_failure=: (bool) -> bool
44
+ def retry_budget=: (RetryBudget | untyped) -> RetryBudget
45
+ def flaky_reporter=: (FlakyReporter::NullReporter | FlakyReporter::JsonlReporter | untyped?) -> FlakyReporter::NullReporter | FlakyReporter::JsonlReporter | untyped
46
+ def flaky_report_path=: (String?) -> FlakyReporter::NullReporter | FlakyReporter::JsonlReporter
47
+ end
48
+
49
+ class RetryBudget
50
+ attr_reader limit: Integer?
51
+ attr_reader used: Integer
52
+
53
+ def initialize: (untyped) -> void
54
+ def consume!: () -> bool
55
+ def remaining: () -> Integer | Float
56
+ def unlimited?: () -> bool
57
+ end
58
+
59
+ class FlakyReporter
60
+ def self.null: () -> NullReporter
61
+ def self.jsonl: (String path) -> JsonlReporter
62
+
63
+ class NullReporter
64
+ def record: (Event event) -> void
65
+ end
66
+
67
+ class JsonlReporter
68
+ def initialize: (String path) -> void
69
+ def record: (Event event) -> void
70
+ end
71
+ end
72
+
73
+ class Event < ::Struct
74
+ attr_accessor schema_version: Integer
75
+ attr_accessor status: Symbol
76
+ attr_accessor retry_reason: Symbol?
77
+ attr_accessor example_id: String
78
+ attr_accessor description: String
79
+ attr_accessor location: String
80
+ attr_accessor attempt: Integer
81
+ attr_accessor retries: Integer
82
+ attr_accessor exception_class: String?
83
+ attr_accessor exception_message: String?
84
+ attr_accessor duration: Numeric
85
+ attr_accessor sleep_seconds: Numeric
86
+ attr_accessor timestamp: String
87
+ end
88
+
89
+ class ExampleContext
90
+ def initialize: (example: untyped) -> void
91
+ def source: () -> untyped
92
+ def metadata: () -> Hash[Symbol, untyped]
93
+ def id: () -> String
94
+ end
95
+
96
+ module ExampleMethods
97
+ def run_with_rewind: (?Hash[Symbol | String, untyped]) -> void
98
+ end
99
+
100
+ class RetryDecision
101
+ def initialize: (exception: Exception?, example: untyped, retry_on: untyped, skip_retry_on: untyped, retry_if: untyped?) -> void
102
+ def retry?: () -> bool
103
+ end
104
+
105
+ class RetryPolicy
106
+ def initialize: (example: untyped, configuration: Configuration, metadata: Hash[Symbol, untyped]?) -> void
107
+ def retry_allowed?: (exception: Exception, retry_on: untyped, skip_retry_on: untyped, retry_if: untyped?) -> bool
108
+ end
109
+
110
+ class RetryCountResolver
111
+ ENV_RETRIES_KEY: String
112
+
113
+ def initialize: (configuration: Configuration, metadata: Hash[Symbol, untyped]?) -> void
114
+ def resolve: (explicit_retries: untyped) -> Integer
115
+ end
116
+
117
+ class RetryDelayResolver
118
+ def initialize: (configuration: Configuration, metadata: Hash[Symbol, untyped]?, example: untyped) -> void
119
+ def resolve: (retry_number: Integer, backoff: untyped, wait: untyped, exception: Exception) -> Float
120
+ end
121
+
122
+ class AttemptRunner
123
+ def run: (run_target: untyped, exception_source: untyped) -> [Exception?, Float, bool]
124
+ end
125
+
126
+ class FlakyTransition
127
+ def initialize: (event_builder: untyped, notifier: untyped) -> void
128
+ def perform: (attempt: Integer, retries: Integer, duration: Numeric) -> void
129
+ end
130
+
131
+ class RetryTransition
132
+ def initialize: (
133
+ configuration: Configuration,
134
+ retry_delay_resolver: untyped,
135
+ event_builder: untyped,
136
+ notifier: untyped,
137
+ state_resetter: untyped,
138
+ sleep: untyped
139
+ ) -> void
140
+ def perform: (
141
+ retry_number: Integer,
142
+ resolved_retries: Integer,
143
+ duration: Numeric,
144
+ exception: Exception,
145
+ backoff: untyped,
146
+ wait: untyped,
147
+ example_source: untyped
148
+ ) -> void
149
+ end
150
+
151
+ class RetryGate
152
+ def initialize: (configuration: Configuration, retry_policy: RetryPolicy, debug: untyped) -> void
153
+ def allow?: (
154
+ exception: Exception,
155
+ retry_number: Integer,
156
+ resolved_retries: Integer,
157
+ retry_on: untyped,
158
+ skip_retry_on: untyped,
159
+ retry_if: untyped?,
160
+ example_id: String
161
+ ) -> bool
162
+ end
163
+
164
+ class RunnerLogger
165
+ def initialize: (configuration: Configuration, warn_output: untyped) -> void
166
+ def reporter_message: (String) -> void
167
+ def debug: (String) -> void
168
+ end
169
+
170
+ class RetryLoop
171
+ def initialize: (
172
+ example: untyped,
173
+ context: ExampleContext,
174
+ retry_count_resolver: RetryCountResolver,
175
+ attempt_runner: AttemptRunner,
176
+ retry_gate: RetryGate,
177
+ retry_transition: RetryTransition,
178
+ flaky_transition: FlakyTransition
179
+ ) -> void
180
+ def run: (
181
+ retries: untyped,
182
+ backoff: untyped,
183
+ wait: untyped,
184
+ retry_on: untyped,
185
+ skip_retry_on: untyped,
186
+ retry_if: untyped
187
+ ) -> void
188
+ end
189
+
190
+ class RunnerComponents
191
+ attr_reader retry_count_resolver: RetryCountResolver
192
+ attr_reader attempt_runner: AttemptRunner
193
+ attr_reader retry_gate: RetryGate
194
+ attr_reader retry_transition: RetryTransition
195
+ attr_reader flaky_transition: FlakyTransition
196
+ attr_reader retry_loop: RetryLoop
197
+
198
+ def initialize: (
199
+ example: untyped,
200
+ configuration: Configuration,
201
+ context: ExampleContext,
202
+ logger: RunnerLogger
203
+ ) -> void
204
+ end
205
+
206
+ class Runner
207
+ def initialize: (example: untyped, configuration: Configuration) -> void
208
+ def run: (?retries: untyped, ?backoff: untyped, ?wait: untyped, ?retry_on: untyped, ?skip_retry_on: untyped, ?retry_if: untyped) -> void
209
+ end
210
+ end
211
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-rewind
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: bin
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.12'
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.12'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '4.0'
32
+ description: |
33
+ rspec-rewind provides deterministic retry policies, exception filters,
34
+ configurable backoff strategies, and flaky test observability for RSpec.
35
+ email:
36
+ - t.yudai92@gmail.com
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - CHANGELOG.md
42
+ - LICENSE.txt
43
+ - README.md
44
+ - lib/rspec-rewind.rb
45
+ - lib/rspec/rewind.rb
46
+ - lib/rspec/rewind/attempt_runner.rb
47
+ - lib/rspec/rewind/backoff.rb
48
+ - lib/rspec/rewind/configuration.rb
49
+ - lib/rspec/rewind/event.rb
50
+ - lib/rspec/rewind/example_context.rb
51
+ - lib/rspec/rewind/example_methods.rb
52
+ - lib/rspec/rewind/example_state_resetter.rb
53
+ - lib/rspec/rewind/flaky_reporter.rb
54
+ - lib/rspec/rewind/flaky_transition.rb
55
+ - lib/rspec/rewind/matcher_validation.rb
56
+ - lib/rspec/rewind/retry_budget.rb
57
+ - lib/rspec/rewind/retry_count_resolver.rb
58
+ - lib/rspec/rewind/retry_decision.rb
59
+ - lib/rspec/rewind/retry_delay_resolver.rb
60
+ - lib/rspec/rewind/retry_event_builder.rb
61
+ - lib/rspec/rewind/retry_gate.rb
62
+ - lib/rspec/rewind/retry_loop.rb
63
+ - lib/rspec/rewind/retry_notifier.rb
64
+ - lib/rspec/rewind/retry_policy.rb
65
+ - lib/rspec/rewind/retry_transition.rb
66
+ - lib/rspec/rewind/runner.rb
67
+ - lib/rspec/rewind/runner_components.rb
68
+ - lib/rspec/rewind/runner_logger.rb
69
+ - lib/rspec/rewind/version.rb
70
+ - rspec-rewind.gemspec
71
+ - sig/rspec/rewind.rbs
72
+ homepage: https://github.com/ydah/rspec-rewind
73
+ licenses:
74
+ - MIT
75
+ metadata:
76
+ homepage_uri: https://github.com/ydah/rspec-rewind
77
+ source_code_uri: https://github.com/ydah/rspec-rewind
78
+ changelog_uri: https://github.com/ydah/rspec-rewind/blob/main/CHANGELOG.md
79
+ rubygems_mfa_required: 'true'
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '3.1'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubygems_version: 4.0.6
95
+ specification_version: 4
96
+ summary: Modern retry orchestration for flaky RSpec examples
97
+ test_files: []