1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
|
module RSpec
module Core
# @private
module Ordering
# @private
# The default global ordering (defined order).
class Identity
def order(items)
items
end
end
# @private
# Orders items randomly.
class Random
def initialize(configuration)
@configuration = configuration
@used = false
end
def used?
@used
end
def order(items)
@used = true
seed = @configuration.seed.to_s
items.sort_by { |item| jenkins_hash_digest(seed + item.id) }
end
private
# http://en.wikipedia.org/wiki/Jenkins_hash_function
# Jenkins provides a good distribution and is simpler than MD5.
# It's a bit slower than MD5 (primarily because `Digest::MD5` is
# implemented in C) but has the advantage of not requiring us
# to load another part of stdlib, which we try to minimize.
def jenkins_hash_digest(string)
hash = 0
string.each_byte do |byte|
hash += byte
hash &= MAX_32_BIT
hash += ((hash << 10) & MAX_32_BIT)
hash &= MAX_32_BIT
hash ^= hash >> 6
end
hash += ((hash << 3) & MAX_32_BIT)
hash &= MAX_32_BIT
hash ^= hash >> 11
hash += ((hash << 15) & MAX_32_BIT)
hash &= MAX_32_BIT
hash
end
MAX_32_BIT = 4_294_967_295
end
# @private
# Orders items by modification time (most recent modified first).
class RecentlyModified
def order(list)
list.sort_by { |item| -File.mtime(item.metadata[:absolute_file_path]).to_i }
end
end
# @private
# Orders items based on a custom block.
class Custom
def initialize(callable)
@callable = callable
end
def order(list)
@callable.call(list)
end
end
# @private
# A strategy which delays looking up the ordering until needed
class Delayed
def initialize(registry, name)
@registry = registry
@name = name
end
def order(list)
strategy.order(list)
end
private
def strategy
@strategy ||= lookup_strategy
end
def lookup_strategy
raise "Undefined ordering strategy #{@name.inspect}" unless @registry.has_strategy?(@name)
@registry.fetch(@name)
end
end
# @private
# Stores the different ordering strategies.
class Registry
def initialize(configuration)
@configuration = configuration
@strategies = {}
register(:random, Random.new(configuration))
register(:recently_modified, RecentlyModified.new)
identity = Identity.new
register(:defined, identity)
# The default global ordering is --defined.
register(:global, identity)
end
def fetch(name, &fallback)
@strategies.fetch(name, &fallback)
end
def has_strategy?(name)
@strategies.key?(name)
end
def register(sym, strategy)
@strategies[sym] = strategy
end
def used_random_seed?
@strategies[:random].used?
end
end
# @private
# Manages ordering configuration.
#
# @note This is not intended to be used externally. Use
# the APIs provided by `RSpec::Core::Configuration` instead.
class ConfigurationManager
attr_reader :seed, :ordering_registry
def initialize
@ordering_registry = Registry.new(self)
@seed = rand(0xFFFF)
@seed_forced = false
@order_forced = false
end
def seed_used?
ordering_registry.used_random_seed?
end
def seed=(seed)
return if @seed_forced
register_ordering(:global, ordering_registry.fetch(:random))
@seed = seed.to_i
end
def order=(type)
order, seed = type.to_s.split(':')
@seed = seed.to_i if seed
ordering_name = if order.include?('rand')
:random
elsif order == 'defined'
:defined
elsif order == 'recently-modified'
:recently_modified
else
order.to_sym
end
if ordering_name
strategy =
if ordering_registry.has_strategy?(ordering_name)
ordering_registry.fetch(ordering_name)
else
Delayed.new(ordering_registry, ordering_name)
end
register_ordering(:global, strategy)
end
end
def force(hash)
if hash.key?(:seed)
self.seed = hash[:seed]
@seed_forced = true
@order_forced = true
elsif hash.key?(:order)
self.order = hash[:order]
@order_forced = true
end
end
def register_ordering(name, strategy=Custom.new(Proc.new { |l| yield l }))
return if @order_forced && name == :global
ordering_registry.register(name, strategy)
end
end
end
end
end
|