rspec-core 3.2.3 → 3.3.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +75 -0
  5. data/README.md +137 -20
  6. data/lib/rspec/autorun.rb +1 -0
  7. data/lib/rspec/core.rb +8 -16
  8. data/lib/rspec/core/backtrace_formatter.rb +1 -3
  9. data/lib/rspec/core/bisect/coordinator.rb +66 -0
  10. data/lib/rspec/core/bisect/example_minimizer.rb +130 -0
  11. data/lib/rspec/core/bisect/runner.rb +139 -0
  12. data/lib/rspec/core/bisect/server.rb +61 -0
  13. data/lib/rspec/core/bisect/subset_enumerator.rb +39 -0
  14. data/lib/rspec/core/configuration.rb +134 -5
  15. data/lib/rspec/core/configuration_options.rb +21 -10
  16. data/lib/rspec/core/example.rb +84 -50
  17. data/lib/rspec/core/example_group.rb +46 -18
  18. data/lib/rspec/core/example_status_persister.rb +235 -0
  19. data/lib/rspec/core/filter_manager.rb +43 -28
  20. data/lib/rspec/core/flat_map.rb +2 -0
  21. data/lib/rspec/core/formatters.rb +30 -20
  22. data/lib/rspec/core/formatters/base_text_formatter.rb +1 -0
  23. data/lib/rspec/core/formatters/bisect_formatter.rb +68 -0
  24. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +115 -0
  25. data/lib/rspec/core/formatters/deprecation_formatter.rb +0 -1
  26. data/lib/rspec/core/formatters/documentation_formatter.rb +0 -4
  27. data/lib/rspec/core/formatters/exception_presenter.rb +389 -0
  28. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  29. data/lib/rspec/core/formatters/helpers.rb +22 -2
  30. data/lib/rspec/core/formatters/html_formatter.rb +1 -4
  31. data/lib/rspec/core/formatters/html_printer.rb +2 -6
  32. data/lib/rspec/core/formatters/json_formatter.rb +6 -4
  33. data/lib/rspec/core/formatters/snippet_extractor.rb +12 -7
  34. data/lib/rspec/core/hooks.rb +8 -2
  35. data/lib/rspec/core/memoized_helpers.rb +77 -17
  36. data/lib/rspec/core/metadata.rb +24 -10
  37. data/lib/rspec/core/metadata_filter.rb +16 -3
  38. data/lib/rspec/core/mutex.rb +63 -0
  39. data/lib/rspec/core/notifications.rb +84 -189
  40. data/lib/rspec/core/option_parser.rb +105 -32
  41. data/lib/rspec/core/ordering.rb +28 -25
  42. data/lib/rspec/core/profiler.rb +32 -0
  43. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +6 -1
  44. data/lib/rspec/core/rake_task.rb +6 -20
  45. data/lib/rspec/core/reentrant_mutex.rb +52 -0
  46. data/lib/rspec/core/reporter.rb +65 -17
  47. data/lib/rspec/core/runner.rb +38 -14
  48. data/lib/rspec/core/set.rb +49 -0
  49. data/lib/rspec/core/shared_example_group.rb +3 -1
  50. data/lib/rspec/core/shell_escape.rb +49 -0
  51. data/lib/rspec/core/version.rb +1 -1
  52. data/lib/rspec/core/world.rb +31 -20
  53. metadata +35 -7
  54. metadata.gz.sig +0 -0
  55. data/lib/rspec/core/backport_random.rb +0 -339
@@ -17,20 +17,23 @@ module RSpec
17
17
  return
18
18
  end
19
19
 
20
- at_exit do
21
- # Don't bother running any specs and just let the program terminate
22
- # if we got here due to an unrescued exception (anything other than
23
- # SystemExit, which is raised when somebody calls Kernel#exit).
24
- next unless $!.nil? || $!.is_a?(SystemExit)
25
-
26
- # We got here because either the end of the program was reached or
27
- # somebody called Kernel#exit. Run the specs and then override any
28
- # existing exit status with RSpec's exit status if any specs failed.
29
- invoke
30
- end
20
+ at_exit { perform_at_exit }
31
21
  @installed_at_exit = true
32
22
  end
33
23
 
24
+ # @private
25
+ def self.perform_at_exit
26
+ # Don't bother running any specs and just let the program terminate
27
+ # if we got here due to an unrescued exception (anything other than
28
+ # SystemExit, which is raised when somebody calls Kernel#exit).
29
+ return unless $!.nil? || $!.is_a?(SystemExit)
30
+
31
+ # We got here because either the end of the program was reached or
32
+ # somebody called Kernel#exit. Run the specs and then override any
33
+ # existing exit status with RSpec's exit status if any specs failed.
34
+ invoke
35
+ end
36
+
34
37
  # Runs the suite of specs and exits the process with an appropriate exit
35
38
  # code.
36
39
  def self.invoke
@@ -83,7 +86,9 @@ module RSpec
83
86
  # @param out [IO] output stream
84
87
  def run(err, out)
85
88
  setup(err, out)
86
- run_specs(@world.ordered_example_groups)
89
+ run_specs(@world.ordered_example_groups).tap do
90
+ persist_example_statuses
91
+ end
87
92
  end
88
93
 
89
94
  # Wires together the various configuration objects and state holders.
@@ -112,6 +117,19 @@ module RSpec
112
117
  end
113
118
  end
114
119
 
120
+ private
121
+
122
+ def persist_example_statuses
123
+ return unless (path = @configuration.example_status_persistence_file_path)
124
+
125
+ ExampleStatusPersister.persist(@world.all_examples, path)
126
+ rescue SystemCallError => e
127
+ RSpec.warning "Could not write example statuses to #{path} (configured as " \
128
+ "`config.example_status_persistence_file_path`) due to a " \
129
+ "system error: #{e.inspect}. Please check that the config " \
130
+ "option is set to an accessible, valid file path", :call_site => nil
131
+ end
132
+
115
133
  # @private
116
134
  def self.disable_autorun!
117
135
  @autorun_disabled = true
@@ -144,8 +162,14 @@ module RSpec
144
162
 
145
163
  # @private
146
164
  def self.trap_interrupt
147
- trap('INT') do
148
- exit!(1) if RSpec.world.wants_to_quit
165
+ trap('INT') { handle_interrupt }
166
+ end
167
+
168
+ # @private
169
+ def self.handle_interrupt
170
+ if RSpec.world.wants_to_quit
171
+ exit!(1)
172
+ else
149
173
  RSpec.world.wants_to_quit = true
150
174
  STDERR.puts "\nRSpec is shutting down and will print the summary report... Interrupt again to force quit."
151
175
  end
@@ -0,0 +1,49 @@
1
+ module RSpec
2
+ module Core
3
+ # @private
4
+ #
5
+ # We use this to replace `::Set` so we can have the advantage of
6
+ # constant time key lookups for unique arrays but without the
7
+ # potential to pollute a developers environment with an extra
8
+ # piece of the stdlib. This helps to prevent false positive
9
+ # builds.
10
+ #
11
+ class Set
12
+ include Enumerable
13
+
14
+ def initialize(array=[])
15
+ @values = {}
16
+ merge(array)
17
+ end
18
+
19
+ def empty?
20
+ @values.empty?
21
+ end
22
+
23
+ def <<(key)
24
+ @values[key] = true
25
+ self
26
+ end
27
+
28
+ def delete(key)
29
+ @values.delete(key)
30
+ end
31
+
32
+ def each(&block)
33
+ @values.keys.each(&block)
34
+ self
35
+ end
36
+
37
+ def include?(key)
38
+ @values.key?(key)
39
+ end
40
+
41
+ def merge(values)
42
+ values.each do |key|
43
+ @values[key] = true
44
+ end
45
+ self
46
+ end
47
+ end
48
+ end
49
+ end
@@ -80,7 +80,7 @@ module RSpec
80
80
  # @see ExampleGroup.include_context
81
81
  def shared_examples(name, *args, &block)
82
82
  top_level = self == ExampleGroup
83
- if top_level && RSpec.thread_local_metadata[:in_example_group]
83
+ if top_level && RSpec::Support.thread_local_data[:in_example_group]
84
84
  raise "Creating isolated shared examples from within a context is " \
85
85
  "not allowed. Remove `RSpec.` prefix or move this to a " \
86
86
  "top-level scope."
@@ -195,10 +195,12 @@ module RSpec
195
195
  if Proc.method_defined?(:source_location)
196
196
  def ensure_block_has_source_location(_block); end
197
197
  else # for 1.8.7
198
+ # :nocov:
198
199
  def ensure_block_has_source_location(block)
199
200
  source_location = yield.split(':')
200
201
  block.extend Module.new { define_method(:source_location) { source_location } }
201
202
  end
203
+ # :nocov:
202
204
  end
203
205
  end
204
206
  end
@@ -0,0 +1,49 @@
1
+ module RSpec
2
+ module Core
3
+ # @private
4
+ # Deals with the fact that `shellwords` only works on POSIX systems.
5
+ module ShellEscape
6
+ module_function
7
+
8
+ def quote(argument)
9
+ "'#{argument.gsub("'", "\\\\'")}'"
10
+ end
11
+
12
+ if RSpec::Support::OS.windows?
13
+ # :nocov:
14
+ alias escape quote
15
+ # :nocov:
16
+ else
17
+ require 'shellwords'
18
+
19
+ def escape(shell_command)
20
+ shell_command.shellescape
21
+ end
22
+ end
23
+
24
+ # Known shells that require quoting: zsh, csh, tcsh.
25
+ #
26
+ # Feel free to add other shells to this list that are known to
27
+ # allow `rspec ./some_spec.rb[1:1]` syntax without quoting the id.
28
+ #
29
+ # @private
30
+ SHELLS_ALLOWING_UNQUOTED_IDS = %w[ bash ksh fish ]
31
+
32
+ def conditionally_quote(id)
33
+ return id if shell_allows_unquoted_ids?
34
+ quote(id)
35
+ end
36
+
37
+ def shell_allows_unquoted_ids?
38
+ # Note: ENV['SHELL'] isn't necessarily the shell the user is currently running.
39
+ # According to http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html:
40
+ # "This variable shall represent a pathname of the user's preferred command language interpreter."
41
+ #
42
+ # It's the best we can easily do, though. We err on the side of safety (quoting
43
+ # the id when not actually needed) so it's not a big deal if the user is actually
44
+ # using a different shell.
45
+ SHELLS_ALLOWING_UNQUOTED_IDS.include?(ENV['SHELL'].to_s.split('/').last)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -3,7 +3,7 @@ module RSpec
3
3
  # Version information for RSpec Core.
4
4
  module Version
5
5
  # Current version of RSpec Core, in semantic versioning format.
6
- STRING = '3.2.3'
6
+ STRING = '3.3.0'
7
7
  end
8
8
  end
9
9
  end
@@ -13,22 +13,12 @@ module RSpec
13
13
  def initialize(configuration=RSpec.configuration)
14
14
  @configuration = configuration
15
15
  @example_groups = []
16
+ @example_group_counts_by_spec_file = Hash.new(0)
16
17
  @filtered_examples = Hash.new do |hash, group|
17
- hash[group] = begin
18
- examples = group.examples.dup
19
- examples = filter_manager.prune(examples)
20
- examples.uniq!
21
- examples
22
- end
18
+ hash[group] = filter_manager.prune(group.examples)
23
19
  end
24
20
  end
25
21
 
26
- # @private
27
- # Used internally to clear remaining groups when fail_fast is set.
28
- def clear_remaining_example_groups
29
- example_groups.clear
30
- end
31
-
32
22
  # @api private
33
23
  #
34
24
  # Apply ordering strategy from configuration to example groups.
@@ -55,9 +45,15 @@ module RSpec
55
45
  # Register an example group.
56
46
  def register(example_group)
57
47
  example_groups << example_group
48
+ @example_group_counts_by_spec_file[example_group.metadata[:file_path]] += 1
58
49
  example_group
59
50
  end
60
51
 
52
+ # @private
53
+ def num_example_groups_defined_in(file)
54
+ @example_group_counts_by_spec_file[file]
55
+ end
56
+
61
57
  # @private
62
58
  def shared_example_group_registry
63
59
  @shared_example_group_registry ||= SharedExampleGroup::Registry.new
@@ -81,6 +77,16 @@ module RSpec
81
77
  inject(0) { |a, e| a + e.filtered_examples.size }
82
78
  end
83
79
 
80
+ # @private
81
+ def all_example_groups
82
+ FlatMap.flat_map(example_groups) { |g| g.descendants }
83
+ end
84
+
85
+ # @private
86
+ def all_examples
87
+ FlatMap.flat_map(all_example_groups) { |g| g.examples }
88
+ end
89
+
84
90
  # @api private
85
91
  #
86
92
  # Find line number of previous declaration.
@@ -99,6 +105,7 @@ module RSpec
99
105
  #
100
106
  # Notify reporter of filters.
101
107
  def announce_filters
108
+ fail_if_config_and_cli_options_invalid
102
109
  filter_announcements = []
103
110
 
104
111
  announce_inclusion_filter filter_announcements
@@ -112,7 +119,7 @@ module RSpec
112
119
  end
113
120
  end
114
121
 
115
- if @configuration.run_all_when_everything_filtered? && example_count.zero?
122
+ if @configuration.run_all_when_everything_filtered? && example_count.zero? && !@configuration.only_failures?
116
123
  reporter.message("#{everything_filtered_message}; ignoring #{inclusion_filter.description}")
117
124
  filtered_examples.clear
118
125
  inclusion_filter.clear
@@ -123,13 +130,7 @@ module RSpec
123
130
  example_groups.clear
124
131
  if filter_manager.empty?
125
132
  reporter.message("No examples found.")
126
- elsif exclusion_filter.empty?
127
- message = everything_filtered_message
128
- if @configuration.run_all_when_everything_filtered?
129
- message << "; ignoring #{inclusion_filter.description}"
130
- end
131
- reporter.message(message)
132
- elsif inclusion_filter.empty?
133
+ elsif exclusion_filter.empty? || inclusion_filter.empty?
133
134
  reporter.message(everything_filtered_message)
134
135
  end
135
136
  end
@@ -162,6 +163,16 @@ module RSpec
162
163
  def declaration_line_numbers
163
164
  @declaration_line_numbers ||= FlatMap.flat_map(example_groups, &:declaration_line_numbers)
164
165
  end
166
+
167
+ def fail_if_config_and_cli_options_invalid
168
+ return unless @configuration.only_failures_but_not_configured?
169
+
170
+ reporter.abort_with(
171
+ "\nTo use `--only-failures`, you must first set " \
172
+ "`config.example_status_persistence_file_path`.",
173
+ 1 # exit code
174
+ )
175
+ end
165
176
  end
166
177
  end
167
178
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.3
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Baker
@@ -46,7 +46,7 @@ cert_chain:
46
46
  ZsVDj6a7lH3cNqtWXZxrb2wO38qV5AkYj8SQK7Hj3/Yui9myUX3crr+PdetazSqQ
47
47
  F3MdtaDehhjC
48
48
  -----END CERTIFICATE-----
49
- date: 2015-04-06 00:00:00.000000000 Z
49
+ date: 2015-06-12 00:00:00.000000000 Z
50
50
  dependencies:
51
51
  - !ruby/object:Gem::Dependency
52
52
  name: rspec-support
@@ -54,14 +54,14 @@ dependencies:
54
54
  requirements:
55
55
  - - "~>"
56
56
  - !ruby/object:Gem::Version
57
- version: 3.2.0
57
+ version: 3.3.0
58
58
  type: :runtime
59
59
  prerelease: false
60
60
  version_requirements: !ruby/object:Gem::Requirement
61
61
  requirements:
62
62
  - - "~>"
63
63
  - !ruby/object:Gem::Version
64
- version: 3.2.0
64
+ version: 3.3.0
65
65
  - !ruby/object:Gem::Dependency
66
66
  name: rake
67
67
  requirement: !ruby/object:Gem::Requirement
@@ -188,6 +188,20 @@ dependencies:
188
188
  - - "~>"
189
189
  - !ruby/object:Gem::Version
190
190
  version: 0.9.0
191
+ - !ruby/object:Gem::Dependency
192
+ name: thread_order
193
+ requirement: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - "~>"
196
+ - !ruby/object:Gem::Version
197
+ version: 1.1.0
198
+ type: :development
199
+ prerelease: false
200
+ version_requirements: !ruby/object:Gem::Requirement
201
+ requirements:
202
+ - - "~>"
203
+ - !ruby/object:Gem::Version
204
+ version: 1.1.0
191
205
  description: BDD for Ruby. RSpec runner and example groups.
192
206
  email: rspec@googlegroups.com
193
207
  executables:
@@ -203,22 +217,31 @@ files:
203
217
  - exe/rspec
204
218
  - lib/rspec/autorun.rb
205
219
  - lib/rspec/core.rb
206
- - lib/rspec/core/backport_random.rb
207
220
  - lib/rspec/core/backtrace_formatter.rb
221
+ - lib/rspec/core/bisect/coordinator.rb
222
+ - lib/rspec/core/bisect/example_minimizer.rb
223
+ - lib/rspec/core/bisect/runner.rb
224
+ - lib/rspec/core/bisect/server.rb
225
+ - lib/rspec/core/bisect/subset_enumerator.rb
208
226
  - lib/rspec/core/configuration.rb
209
227
  - lib/rspec/core/configuration_options.rb
210
228
  - lib/rspec/core/drb.rb
211
229
  - lib/rspec/core/dsl.rb
212
230
  - lib/rspec/core/example.rb
213
231
  - lib/rspec/core/example_group.rb
232
+ - lib/rspec/core/example_status_persister.rb
214
233
  - lib/rspec/core/filter_manager.rb
215
234
  - lib/rspec/core/flat_map.rb
216
235
  - lib/rspec/core/formatters.rb
217
236
  - lib/rspec/core/formatters/base_formatter.rb
218
237
  - lib/rspec/core/formatters/base_text_formatter.rb
238
+ - lib/rspec/core/formatters/bisect_formatter.rb
239
+ - lib/rspec/core/formatters/bisect_progress_formatter.rb
219
240
  - lib/rspec/core/formatters/console_codes.rb
220
241
  - lib/rspec/core/formatters/deprecation_formatter.rb
221
242
  - lib/rspec/core/formatters/documentation_formatter.rb
243
+ - lib/rspec/core/formatters/exception_presenter.rb
244
+ - lib/rspec/core/formatters/fallback_message_formatter.rb
222
245
  - lib/rspec/core/formatters/helpers.rb
223
246
  - lib/rspec/core/formatters/html_formatter.rb
224
247
  - lib/rspec/core/formatters/html_printer.rb
@@ -237,20 +260,25 @@ files:
237
260
  - lib/rspec/core/mocking_adapters/null.rb
238
261
  - lib/rspec/core/mocking_adapters/rr.rb
239
262
  - lib/rspec/core/mocking_adapters/rspec.rb
263
+ - lib/rspec/core/mutex.rb
240
264
  - lib/rspec/core/notifications.rb
241
265
  - lib/rspec/core/option_parser.rb
242
266
  - lib/rspec/core/ordering.rb
243
267
  - lib/rspec/core/pending.rb
268
+ - lib/rspec/core/profiler.rb
244
269
  - lib/rspec/core/project_initializer.rb
245
270
  - lib/rspec/core/project_initializer/.rspec
246
271
  - lib/rspec/core/project_initializer/spec/spec_helper.rb
247
272
  - lib/rspec/core/rake_task.rb
273
+ - lib/rspec/core/reentrant_mutex.rb
248
274
  - lib/rspec/core/reporter.rb
249
275
  - lib/rspec/core/ruby_project.rb
250
276
  - lib/rspec/core/runner.rb
251
277
  - lib/rspec/core/sandbox.rb
278
+ - lib/rspec/core/set.rb
252
279
  - lib/rspec/core/shared_context.rb
253
280
  - lib/rspec/core/shared_example_group.rb
281
+ - lib/rspec/core/shell_escape.rb
254
282
  - lib/rspec/core/test_unit_assertions_adapter.rb
255
283
  - lib/rspec/core/version.rb
256
284
  - lib/rspec/core/warnings.rb
@@ -275,10 +303,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
275
303
  - !ruby/object:Gem::Version
276
304
  version: '0'
277
305
  requirements: []
278
- rubyforge_project: rspec
306
+ rubyforge_project:
279
307
  rubygems_version: 2.2.2
280
308
  signing_key:
281
309
  specification_version: 4
282
- summary: rspec-core-3.2.3
310
+ summary: rspec-core-3.3.0
283
311
  test_files: []
284
312
  has_rdoc:
metadata.gz.sig CHANGED
Binary file
@@ -1,339 +0,0 @@
1
- module RSpec
2
- module Core
3
- # @private
4
- #
5
- # Methods used internally by the backports.
6
- #
7
- # This code was (mostly) ported from the backports gem found at
8
- # https://github.com/marcandre/backports which is subject to this license:
9
- #
10
- # =========================================================================
11
- #
12
- # Copyright (c) 2009 Marc-Andre Lafortune
13
- #
14
- # Permission is hereby granted, free of charge, to any person obtaining
15
- # a copy of this software and associated documentation files (the
16
- # "Software"), to deal in the Software without restriction, including
17
- # without limitation the rights to use, copy, modify, merge, publish,
18
- # distribute, sublicense, and/or sell copies of the Software, and to
19
- # permit persons to whom the Software is furnished to do so, subject to
20
- # the following conditions:
21
- #
22
- # The above copyright notice and this permission notice shall be
23
- # included in all copies or substantial portions of the Software.
24
- #
25
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
- #
33
- # =========================================================================
34
- #
35
- # The goal is to provide a random number generator in Ruby versions that do
36
- # not have one. This was added to support localization of random spec
37
- # ordering.
38
- #
39
- # These were in multiple files in backports, but merged into one here.
40
- module Backports
41
- # Helper method to coerce a value into a specific class.
42
- # Raises a TypeError if the coercion fails or the returned value
43
- # is not of the right class.
44
- # (from Rubinius)
45
- def self.coerce_to(obj, cls, meth)
46
- return obj if obj.kind_of?(cls)
47
-
48
- begin
49
- ret = obj.__send__(meth)
50
- rescue Exception => e
51
- raise TypeError, "Coercion error: #{obj.inspect}.#{meth} => #{cls} failed:\n" \
52
- "(#{e.message})"
53
- end
54
- raise TypeError, "Coercion error: obj.#{meth} did NOT return a #{cls} (was #{ret.class})" unless ret.kind_of? cls
55
- ret
56
- end
57
-
58
- # @private
59
- def self.coerce_to_int(obj)
60
- coerce_to(obj, Integer, :to_int)
61
- end
62
-
63
- # Used internally to make it easy to deal with optional arguments.
64
- # (from Rubinius)
65
- Undefined = Object.new
66
-
67
- # @private
68
- class Random
69
- # @private
70
- # An implementation of Mersenne Twister MT19937 in Ruby.
71
- class MT19937
72
- STATE_SIZE = 624
73
- LAST_STATE = STATE_SIZE - 1
74
- PAD_32_BITS = 0xffffffff
75
-
76
- # See seed=
77
- def initialize(seed)
78
- self.seed = seed
79
- end
80
-
81
- LAST_31_BITS = 0x7fffffff
82
- OFFSET = 397
83
-
84
- # Generates a completely new state out of the previous one.
85
- def next_state
86
- STATE_SIZE.times do |i|
87
- mix = @state[i] & 0x80000000 | @state[i+1 - STATE_SIZE] & 0x7fffffff
88
- @state[i] = @state[i+OFFSET - STATE_SIZE] ^ (mix >> 1)
89
- @state[i] ^= 0x9908b0df if mix.odd?
90
- end
91
- @last_read = -1
92
- end
93
-
94
- # Seed must be either an Integer (only the first 32 bits will be used)
95
- # or an Array of Integers (of which only the first 32 bits will be
96
- # used).
97
- #
98
- # No conversion or type checking is done at this level.
99
- def seed=(seed)
100
- case seed
101
- when Integer
102
- @state = Array.new(STATE_SIZE)
103
- @state[0] = seed & PAD_32_BITS
104
- (1..LAST_STATE).each do |i|
105
- @state[i] = (1812433253 * (@state[i-1] ^ @state[i-1]>>30) + i)& PAD_32_BITS
106
- end
107
- @last_read = LAST_STATE
108
- when Array
109
- self.seed = 19650218
110
- i=1
111
- j=0
112
- [STATE_SIZE, seed.size].max.times do
113
- @state[i] = (@state[i] ^ (@state[i-1] ^ @state[i-1]>>30) * 1664525) + j + seed[j] & PAD_32_BITS
114
- if (i+=1) >= STATE_SIZE
115
- @state[0] = @state[-1]
116
- i = 1
117
- end
118
- j = 0 if (j+=1) >= seed.size
119
- end
120
- (STATE_SIZE-1).times do
121
- @state[i] = (@state[i] ^ (@state[i-1] ^ @state[i-1]>>30) * 1566083941) - i & PAD_32_BITS
122
- if (i+=1) >= STATE_SIZE
123
- @state[0] = @state[-1]
124
- i = 1
125
- end
126
- end
127
- @state[0] = 0x80000000
128
- else
129
- raise ArgumentError, "Seed must be an Integer or an Array"
130
- end
131
- end
132
-
133
- # Returns a random Integer from the range 0 ... (1 << 32).
134
- def random_32_bits
135
- next_state if @last_read >= LAST_STATE
136
- @last_read += 1
137
- y = @state[@last_read]
138
- # Tempering
139
- y ^= (y >> 11)
140
- y ^= (y << 7) & 0x9d2c5680
141
- y ^= (y << 15) & 0xefc60000
142
- y ^= (y >> 18)
143
- end
144
-
145
- # Supplement the MT19937 class with methods to do
146
- # conversions the same way as MRI.
147
- # No argument checking is done here either.
148
-
149
- FLOAT_FACTOR = 1.0/9007199254740992.0
150
- # Generates a random number on [0, 1) with 53-bit resolution.
151
- def random_float
152
- ((random_32_bits >> 5) * 67108864.0 + (random_32_bits >> 6)) * FLOAT_FACTOR;
153
- end
154
-
155
- # Returns an integer within 0...upto.
156
- def random_integer(upto)
157
- n = upto - 1
158
- nb_full_32 = 0
159
- while n > PAD_32_BITS
160
- n >>= 32
161
- nb_full_32 += 1
162
- end
163
- mask = mask_32_bits(n)
164
- begin
165
- rand = random_32_bits & mask
166
- nb_full_32.times do
167
- rand <<= 32
168
- rand |= random_32_bits
169
- end
170
- end until rand < upto
171
- rand
172
- end
173
-
174
- def random_bytes(nb)
175
- nb_32_bits = (nb + 3) / 4
176
- random = nb_32_bits.times.map { random_32_bits }
177
- random.pack("L" * nb_32_bits)[0, nb]
178
- end
179
-
180
- def state_as_bignum
181
- b = 0
182
- @state.each_with_index do |val, i|
183
- b |= val << (32 * i)
184
- end
185
- b
186
- end
187
-
188
- def left # It's actually the number of words left + 1, as per MRI...
189
- MT19937::STATE_SIZE - @last_read
190
- end
191
-
192
- def marshal_dump
193
- [state_as_bignum, left]
194
- end
195
-
196
- def marshal_load(ary)
197
- b, left = ary
198
- @last_read = MT19937::STATE_SIZE - left
199
- @state = Array.new(STATE_SIZE)
200
- STATE_SIZE.times do |i|
201
- @state[i] = b & PAD_32_BITS
202
- b >>= 32
203
- end
204
- end
205
-
206
- # Convert an Integer seed of arbitrary size to either a single 32 bit
207
- # integer, or an Array of 32 bit integers.
208
- def self.convert_seed(seed)
209
- seed = seed.abs
210
- long_values = []
211
- begin
212
- long_values << (seed & PAD_32_BITS)
213
- seed >>= 32
214
- end until seed == 0
215
-
216
- # Done to allow any kind of sequence of integers.
217
- long_values.pop if long_values[-1] == 1 && long_values.size > 1
218
-
219
- long_values.size > 1 ? long_values : long_values.first
220
- end
221
-
222
- def self.[](seed)
223
- new(convert_seed(seed))
224
- end
225
-
226
- private
227
-
228
- MASK_BY = [1,2,4,8,16]
229
- def mask_32_bits(n)
230
- MASK_BY.each do |shift|
231
- n |= n >> shift
232
- end
233
- n
234
- end
235
- end
236
-
237
- # @private
238
- # Implementation corresponding to the actual Random class of Ruby
239
- # The actual random generator (mersenne twister) is in MT19937.
240
- # Ruby specific conversions are handled in bits_and_bytes.
241
- # The high level stuff (argument checking) is done here.
242
- module Implementation
243
- attr_reader :seed
244
-
245
- def initialize(seed = 0)
246
- super()
247
- seed_rand seed
248
- end
249
-
250
- def seed_rand(new_seed = 0)
251
- new_seed = Backports.coerce_to_int(new_seed)
252
- @seed = nil unless defined?(@seed)
253
- old, @seed = @seed, new_seed.nonzero? || Random.new_seed
254
- @mt = MT19937[ @seed ]
255
- old
256
- end
257
-
258
- def rand(limit = Backports::Undefined)
259
- case limit
260
- when Backports::Undefined
261
- @mt.random_float
262
- when Float
263
- limit * @mt.random_float unless limit <= 0
264
- when Range
265
- _rand_range(limit)
266
- else
267
- limit = Backports.coerce_to_int(limit)
268
- @mt.random_integer(limit) unless limit <= 0
269
- end || raise(ArgumentError, "invalid argument #{limit}")
270
- end
271
-
272
- def bytes(nb)
273
- nb = Backports.coerce_to_int(nb)
274
- raise ArgumentError, "negative size" if nb < 0
275
- @mt.random_bytes(nb)
276
- end
277
-
278
- def ==(other)
279
- other.is_a?(Random) &&
280
- seed == other.seed &&
281
- left == other.send(:left) &&
282
- state == other.send(:state)
283
- end
284
-
285
- def marshal_dump
286
- @mt.marshal_dump << @seed
287
- end
288
-
289
- def marshal_load(ary)
290
- @seed = ary.pop
291
- @mt = MT19937.allocate
292
- @mt.marshal_load(ary)
293
- end
294
-
295
- private
296
-
297
- def state
298
- @mt.state_as_bignum
299
- end
300
-
301
- def left
302
- @mt.left
303
- end
304
-
305
- def _rand_range(limit)
306
- range = limit.end - limit.begin
307
- if (!range.is_a?(Float)) && range.respond_to?(:to_int) && range = Backports.coerce_to_int(range)
308
- range += 1 unless limit.exclude_end?
309
- limit.begin + @mt.random_integer(range) unless range <= 0
310
- elsif range = Backports.coerce_to(range, Float, :to_f)
311
- if range < 0
312
- nil
313
- elsif limit.exclude_end?
314
- limit.begin + @mt.random_float * range unless range <= 0
315
- else
316
- # cheat a bit... this will reduce the nb of random bits
317
- loop do
318
- r = @mt.random_float * range * 1.0001
319
- break limit.begin + r unless r > range
320
- end
321
- end
322
- end
323
- end
324
- end
325
-
326
- def self.new_seed
327
- (2 ** 62) + Kernel.rand(2 ** 62)
328
- end
329
- end
330
-
331
- class Random
332
- include Implementation
333
- class << self
334
- include Implementation
335
- end
336
- end
337
- end
338
- end
339
- end