rspec-core 3.2.3 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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