activesupport 3.1.0.beta1 → 3.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- data/CHANGELOG +33 -1
- data/README.rdoc +1 -1
- data/lib/active_support.rb +1 -1
- data/lib/active_support/buffered_logger.rb +9 -4
- data/lib/active_support/configurable.rb +9 -6
- data/lib/active_support/core_ext/array/conversions.rb +2 -2
- data/lib/active_support/core_ext/array/random_access.rb +1 -1
- data/lib/active_support/core_ext/array/uniq_by.rb +2 -3
- data/lib/active_support/core_ext/class/inheritable_attributes.rb +0 -2
- data/lib/active_support/core_ext/date/calculations.rb +3 -3
- data/lib/active_support/core_ext/date/conversions.rb +3 -2
- data/lib/active_support/core_ext/date_time/conversions.rb +8 -8
- data/lib/active_support/core_ext/hash/conversions.rb +2 -1
- data/lib/active_support/core_ext/hash/indifferent_access.rb +12 -0
- data/lib/active_support/core_ext/hash/slice.rb +1 -1
- data/lib/active_support/core_ext/integer/inflections.rb +7 -4
- data/lib/active_support/core_ext/kernel/reporting.rb +13 -1
- data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +1 -0
- data/lib/active_support/core_ext/module/deprecation.rb +0 -2
- data/lib/active_support/core_ext/numeric/time.rb +2 -0
- data/lib/active_support/core_ext/object/blank.rb +3 -3
- data/lib/active_support/core_ext/object/duplicable.rb +2 -0
- data/lib/active_support/core_ext/object/try.rb +2 -0
- data/lib/active_support/core_ext/object/with_options.rb +8 -5
- data/lib/active_support/core_ext/string/behavior.rb +1 -2
- data/lib/active_support/core_ext/string/exclude.rb +1 -1
- data/lib/active_support/core_ext/string/inquiry.rb +1 -1
- data/lib/active_support/core_ext/time/calculations.rb +4 -3
- data/lib/active_support/core_ext/time/marshal.rb +1 -0
- data/lib/active_support/descendants_tracker.rb +9 -7
- data/lib/active_support/duration.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +12 -4
- data/lib/active_support/inflector/methods.rb +5 -3
- data/lib/active_support/json/encoding.rb +4 -1
- data/lib/active_support/log_subscriber/test_helper.rb +1 -0
- data/lib/active_support/ordered_hash.rb +4 -0
- data/lib/active_support/secure_random.rb +3 -202
- data/lib/active_support/testing/performance.rb +227 -335
- data/lib/active_support/testing/performance/jruby.rb +115 -0
- data/lib/active_support/testing/performance/rubinius.rb +113 -0
- data/lib/active_support/testing/performance/ruby.rb +152 -0
- data/lib/active_support/testing/performance/ruby/mri.rb +59 -0
- data/lib/active_support/testing/performance/ruby/yarv.rb +57 -0
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini.rb +4 -1
- metadata +8 -3
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'active_support/string_inquirer'
|
2
2
|
|
3
3
|
class String
|
4
|
-
# Wraps the current string in the ActiveSupport::StringInquirer class,
|
4
|
+
# Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class,
|
5
5
|
# which gives you a prettier way to test for equality. Example:
|
6
6
|
#
|
7
7
|
# env = "production".inquiry
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/duration'
|
2
|
+
require 'active_support/core_ext/time/zones'
|
2
3
|
|
3
4
|
class Time
|
4
5
|
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
@@ -187,7 +188,7 @@ class Time
|
|
187
188
|
# Returns a new Time representing the start of the day (0:00)
|
188
189
|
def beginning_of_day
|
189
190
|
#(self - seconds_since_midnight).change(:usec => 0)
|
190
|
-
change(:hour => 0
|
191
|
+
change(:hour => 0)
|
191
192
|
end
|
192
193
|
alias :midnight :beginning_of_day
|
193
194
|
alias :at_midnight :beginning_of_day
|
@@ -201,7 +202,7 @@ class Time
|
|
201
202
|
# Returns a new Time representing the start of the month (1st of the month, 0:00)
|
202
203
|
def beginning_of_month
|
203
204
|
#self - ((self.mday-1).days + self.seconds_since_midnight)
|
204
|
-
change(:day => 1
|
205
|
+
change(:day => 1, :hour => 0)
|
205
206
|
end
|
206
207
|
alias :at_beginning_of_month :beginning_of_month
|
207
208
|
|
@@ -227,7 +228,7 @@ class Time
|
|
227
228
|
|
228
229
|
# Returns a new Time representing the start of the year (1st of january, 0:00)
|
229
230
|
def beginning_of_year
|
230
|
-
change(:month => 1, :day => 1, :hour => 0
|
231
|
+
change(:month => 1, :day => 1, :hour => 0)
|
231
232
|
end
|
232
233
|
alias :at_beginning_of_year :beginning_of_year
|
233
234
|
|
@@ -37,6 +37,7 @@ if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone
|
|
37
37
|
time.instance_eval do
|
38
38
|
if zone = defined?(@_zone) && remove_instance_variable('@_zone')
|
39
39
|
ary = to_a
|
40
|
+
ary[0] += subsec if ary[0] == sec
|
40
41
|
ary[-1] = zone
|
41
42
|
utc? ? Time.utc(*ary) : Time.local(*ary)
|
42
43
|
else
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/dependencies'
|
2
|
-
|
3
1
|
module ActiveSupport
|
4
2
|
# This module provides an internal implementation to track descendants
|
5
3
|
# which is faster than iterating through ObjectSpace.
|
@@ -18,12 +16,16 @@ module ActiveSupport
|
|
18
16
|
end
|
19
17
|
|
20
18
|
def self.clear
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
19
|
+
if defined? ActiveSupport::Dependencies
|
20
|
+
@@direct_descendants.each do |klass, descendants|
|
21
|
+
if ActiveSupport::Dependencies.autoloaded?(klass)
|
22
|
+
@@direct_descendants.delete(klass)
|
23
|
+
else
|
24
|
+
descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
|
25
|
+
end
|
26
26
|
end
|
27
|
+
else
|
28
|
+
@@direct_descendants.clear
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
@@ -10,6 +10,7 @@ module ActiveSupport
|
|
10
10
|
# 1.month.ago # equivalent to Time.now.advance(:months => -1)
|
11
11
|
class Duration < BasicObject
|
12
12
|
attr_accessor :value, :parts
|
13
|
+
delegate :duplicable?, :to => :value # required when using ActiveSupport's BasicObject on 1.8
|
13
14
|
|
14
15
|
def initialize(value, parts) #:nodoc:
|
15
16
|
@value, @parts = value, parts
|
@@ -10,6 +10,10 @@ module ActiveSupport
|
|
10
10
|
true
|
11
11
|
end
|
12
12
|
|
13
|
+
def with_indifferent_access
|
14
|
+
dup
|
15
|
+
end
|
16
|
+
|
13
17
|
def initialize(constructor = {})
|
14
18
|
if constructor.is_a?(Hash)
|
15
19
|
super()
|
@@ -58,8 +62,12 @@ module ActiveSupport
|
|
58
62
|
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
59
63
|
#
|
60
64
|
def update(other_hash)
|
61
|
-
other_hash.
|
62
|
-
|
65
|
+
if other_hash.is_a? HashWithIndifferentAccess
|
66
|
+
super(other_hash)
|
67
|
+
else
|
68
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
69
|
+
self
|
70
|
+
end
|
63
71
|
end
|
64
72
|
|
65
73
|
alias_method :merge!, :update
|
@@ -140,8 +148,8 @@ module ActiveSupport
|
|
140
148
|
end
|
141
149
|
|
142
150
|
def convert_value(value)
|
143
|
-
if value.
|
144
|
-
|
151
|
+
if value.is_a? Hash
|
152
|
+
value.nested_under_indifferent_access
|
145
153
|
elsif value.is_a?(Array)
|
146
154
|
value.dup.replace(value.map { |e| convert_value(e) })
|
147
155
|
else
|
@@ -135,11 +135,13 @@ module ActiveSupport
|
|
135
135
|
# ordinalize(2) # => "2nd"
|
136
136
|
# ordinalize(1002) # => "1002nd"
|
137
137
|
# ordinalize(1003) # => "1003rd"
|
138
|
+
# ordinalize(-11) # => "-11th"
|
139
|
+
# ordinalize(-1021) # => "-1021st"
|
138
140
|
def ordinalize(number)
|
139
|
-
if (11..13).include?(number.to_i % 100)
|
141
|
+
if (11..13).include?(number.to_i.abs % 100)
|
140
142
|
"#{number}th"
|
141
143
|
else
|
142
|
-
case number.to_i % 10
|
144
|
+
case number.to_i.abs % 10
|
143
145
|
when 1; "#{number}st"
|
144
146
|
when 2; "#{number}nd"
|
145
147
|
when 3; "#{number}rd"
|
@@ -148,4 +150,4 @@ module ActiveSupport
|
|
148
150
|
end
|
149
151
|
end
|
150
152
|
end
|
151
|
-
end
|
153
|
+
end
|
@@ -2,6 +2,7 @@ require 'active_support/core_ext/object/to_json'
|
|
2
2
|
require 'active_support/core_ext/module/delegation'
|
3
3
|
require 'active_support/deprecation'
|
4
4
|
require 'active_support/json/variable'
|
5
|
+
require 'active_support/ordered_hash'
|
5
6
|
|
6
7
|
require 'bigdecimal'
|
7
8
|
require 'active_support/core_ext/big_decimal/conversions' # for #to_s
|
@@ -205,7 +206,9 @@ class Regexp
|
|
205
206
|
end
|
206
207
|
|
207
208
|
module Enumerable
|
208
|
-
def as_json(options = nil)
|
209
|
+
def as_json(options = nil) #:nodoc:
|
210
|
+
to_a.as_json(options)
|
211
|
+
end
|
209
212
|
end
|
210
213
|
|
211
214
|
class Array
|
@@ -1,205 +1,6 @@
|
|
1
|
-
|
2
|
-
require 'securerandom'
|
3
|
-
rescue LoadError
|
4
|
-
end
|
1
|
+
require 'securerandom'
|
5
2
|
|
6
3
|
module ActiveSupport
|
7
|
-
|
8
|
-
|
9
|
-
SecureRandom = ::SecureRandom # :nodoc:
|
10
|
-
else
|
11
|
-
# = Secure random number generator interface.
|
12
|
-
#
|
13
|
-
# This library is an interface for secure random number generator which is
|
14
|
-
# suitable for generating session key in HTTP cookies, etc.
|
15
|
-
#
|
16
|
-
# It supports following secure random number generators.
|
17
|
-
#
|
18
|
-
# * openssl
|
19
|
-
# * /dev/urandom
|
20
|
-
# * Win32
|
21
|
-
#
|
22
|
-
# *Note*: This module is based on the SecureRandom library from Ruby 1.9,
|
23
|
-
# revision 18786, August 23 2008. It's 100% interface-compatible with Ruby 1.9's
|
24
|
-
# SecureRandom library.
|
25
|
-
#
|
26
|
-
# == Example
|
27
|
-
#
|
28
|
-
# # random hexadecimal string.
|
29
|
-
# p SecureRandom.hex(10) # => "52750b30ffbc7de3b362"
|
30
|
-
# p SecureRandom.hex(10) # => "92b15d6c8dc4beb5f559"
|
31
|
-
# p SecureRandom.hex(11) # => "6aca1b5c58e4863e6b81b8"
|
32
|
-
# p SecureRandom.hex(12) # => "94b2fff3e7fd9b9c391a2306"
|
33
|
-
# p SecureRandom.hex(13) # => "39b290146bea6ce975c37cfc23"
|
34
|
-
# ...
|
35
|
-
#
|
36
|
-
# # random base64 string.
|
37
|
-
# p SecureRandom.base64(10) # => "EcmTPZwWRAozdA=="
|
38
|
-
# p SecureRandom.base64(10) # => "9b0nsevdwNuM/w=="
|
39
|
-
# p SecureRandom.base64(10) # => "KO1nIU+p9DKxGg=="
|
40
|
-
# p SecureRandom.base64(11) # => "l7XEiFja+8EKEtY="
|
41
|
-
# p SecureRandom.base64(12) # => "7kJSM/MzBJI+75j8"
|
42
|
-
# p SecureRandom.base64(13) # => "vKLJ0tXBHqQOuIcSIg=="
|
43
|
-
# ...
|
44
|
-
#
|
45
|
-
# # random binary string.
|
46
|
-
# p SecureRandom.random_bytes(10) # => "\016\t{\370g\310pbr\301"
|
47
|
-
# p SecureRandom.random_bytes(10) # => "\323U\030TO\234\357\020\a\337"
|
48
|
-
# ...
|
49
|
-
#
|
50
|
-
module SecureRandom
|
51
|
-
|
52
|
-
# Generates a random binary string.
|
53
|
-
#
|
54
|
-
# The argument n specifies the length of the result string.
|
55
|
-
#
|
56
|
-
# If n is not specified, 16 is assumed.
|
57
|
-
# It may be larger in future.
|
58
|
-
#
|
59
|
-
# If secure random number generator is not available,
|
60
|
-
# NotImplementedError is raised.
|
61
|
-
#
|
62
|
-
def self.random_bytes(n=nil)
|
63
|
-
n ||= 16
|
64
|
-
|
65
|
-
unless defined? OpenSSL
|
66
|
-
begin
|
67
|
-
require 'openssl'
|
68
|
-
rescue LoadError
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
if defined? OpenSSL::Random
|
73
|
-
return OpenSSL::Random.random_bytes(n)
|
74
|
-
end
|
75
|
-
|
76
|
-
if !defined?(@has_urandom) || @has_urandom
|
77
|
-
flags = File::RDONLY
|
78
|
-
flags |= File::NONBLOCK if defined? File::NONBLOCK
|
79
|
-
flags |= File::NOCTTY if defined? File::NOCTTY
|
80
|
-
flags |= File::NOFOLLOW if defined? File::NOFOLLOW
|
81
|
-
begin
|
82
|
-
File.open("/dev/urandom", flags) {|f|
|
83
|
-
unless f.stat.chardev?
|
84
|
-
raise Errno::ENOENT
|
85
|
-
end
|
86
|
-
@has_urandom = true
|
87
|
-
ret = f.readpartial(n)
|
88
|
-
if ret.length != n
|
89
|
-
raise NotImplementedError, "Unexpected partial read from random device"
|
90
|
-
end
|
91
|
-
return ret
|
92
|
-
}
|
93
|
-
rescue Errno::ENOENT
|
94
|
-
@has_urandom = false
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
unless defined?(@has_win32)
|
99
|
-
begin
|
100
|
-
require 'Win32API'
|
101
|
-
|
102
|
-
crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L')
|
103
|
-
@crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L')
|
104
|
-
|
105
|
-
hProvStr = " " * 4
|
106
|
-
prov_rsa_full = 1
|
107
|
-
crypt_verifycontext = 0xF0000000
|
108
|
-
|
109
|
-
if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0
|
110
|
-
raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
|
111
|
-
end
|
112
|
-
@hProv, = hProvStr.unpack('L')
|
113
|
-
|
114
|
-
@has_win32 = true
|
115
|
-
rescue LoadError
|
116
|
-
@has_win32 = false
|
117
|
-
end
|
118
|
-
end
|
119
|
-
if @has_win32
|
120
|
-
bytes = " " * n
|
121
|
-
if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0
|
122
|
-
raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
|
123
|
-
end
|
124
|
-
return bytes
|
125
|
-
end
|
126
|
-
|
127
|
-
raise NotImplementedError, "No random device"
|
128
|
-
end
|
129
|
-
|
130
|
-
# Generates a random hex string.
|
131
|
-
#
|
132
|
-
# The argument n specifies the length of the random length.
|
133
|
-
# The length of the result string is twice of n.
|
134
|
-
#
|
135
|
-
# If n is not specified, 16 is assumed.
|
136
|
-
# It may be larger in future.
|
137
|
-
#
|
138
|
-
# If secure random number generator is not available,
|
139
|
-
# NotImplementedError is raised.
|
140
|
-
#
|
141
|
-
def self.hex(n=nil)
|
142
|
-
random_bytes(n).unpack("H*")[0]
|
143
|
-
end
|
144
|
-
|
145
|
-
# Generates a random base64 string.
|
146
|
-
#
|
147
|
-
# The argument n specifies the length of the random length.
|
148
|
-
# The length of the result string is about 4/3 of n.
|
149
|
-
#
|
150
|
-
# If n is not specified, 16 is assumed.
|
151
|
-
# It may be larger in future.
|
152
|
-
#
|
153
|
-
# If secure random number generator is not available,
|
154
|
-
# NotImplementedError is raised.
|
155
|
-
#
|
156
|
-
def self.base64(n=nil)
|
157
|
-
[random_bytes(n)].pack("m*").delete("\n")
|
158
|
-
end
|
159
|
-
|
160
|
-
# Generates a random number.
|
161
|
-
#
|
162
|
-
# If an positive integer is given as n,
|
163
|
-
# SecureRandom.random_number returns an integer:
|
164
|
-
# 0 <= SecureRandom.random_number(n) < n.
|
165
|
-
#
|
166
|
-
# If 0 is given or an argument is not given,
|
167
|
-
# SecureRandom.random_number returns an float:
|
168
|
-
# 0.0 <= SecureRandom.random_number() < 1.0.
|
169
|
-
#
|
170
|
-
def self.random_number(n=0)
|
171
|
-
if 0 < n
|
172
|
-
hex = n.to_s(16)
|
173
|
-
hex = '0' + hex if (hex.length & 1) == 1
|
174
|
-
bin = [hex].pack("H*")
|
175
|
-
mask = bin[0]
|
176
|
-
mask |= mask >> 1
|
177
|
-
mask |= mask >> 2
|
178
|
-
mask |= mask >> 4
|
179
|
-
begin
|
180
|
-
rnd = SecureRandom.random_bytes(bin.length)
|
181
|
-
rnd[0] = rnd[0] & mask
|
182
|
-
end until rnd < bin
|
183
|
-
rnd.unpack("H*")[0].hex
|
184
|
-
else
|
185
|
-
# assumption: Float::MANT_DIG <= 64
|
186
|
-
i64 = SecureRandom.random_bytes(8).unpack("Q")[0]
|
187
|
-
Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG)
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# Following code is based on David Garamond's GUID library for Ruby.
|
192
|
-
def self.lastWin32ErrorMessage # :nodoc:
|
193
|
-
get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
|
194
|
-
format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L')
|
195
|
-
format_message_ignore_inserts = 0x00000200
|
196
|
-
format_message_from_system = 0x00001000
|
197
|
-
|
198
|
-
code = get_last_error.call
|
199
|
-
msg = "\0" * 1024
|
200
|
-
len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil)
|
201
|
-
msg[0, len].tr("\r", '').chomp
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
4
|
+
# Use Ruby's SecureRandom library.
|
5
|
+
SecureRandom = ::SecureRandom # :nodoc:
|
205
6
|
end
|
@@ -1,38 +1,83 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
module
|
10
|
-
module
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rails/version'
|
3
|
+
require 'active_support/concern'
|
4
|
+
require 'active_support/core_ext/class/delegating_attributes'
|
5
|
+
require 'active_support/core_ext/string/inflections'
|
6
|
+
require 'action_view/helpers/number_helper'
|
7
|
+
|
8
|
+
module ActiveSupport
|
9
|
+
module Testing
|
10
|
+
module Performance
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
included do
|
14
|
+
superclass_delegating_accessor :profile_options
|
15
|
+
self.profile_options = {}
|
16
|
+
|
17
|
+
if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions
|
18
|
+
include ForMiniTest
|
19
|
+
else
|
20
|
+
include ForClassicTestUnit
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# each implementation should define metrics and freeze the defaults
|
25
|
+
DEFAULTS =
|
26
|
+
if ARGV.include?('--benchmark') # HAX for rake test
|
27
|
+
{ :runs => 4,
|
28
|
+
:output => 'tmp/performance',
|
29
|
+
:benchmark => true }
|
30
|
+
else
|
31
|
+
{ :runs => 1,
|
32
|
+
:output => 'tmp/performance',
|
33
|
+
:benchmark => false }
|
30
34
|
end
|
35
|
+
|
36
|
+
def full_profile_options
|
37
|
+
DEFAULTS.merge(profile_options)
|
38
|
+
end
|
31
39
|
|
32
|
-
|
33
|
-
|
40
|
+
def full_test_name
|
41
|
+
"#{self.class.name}##{method_name}"
|
42
|
+
end
|
43
|
+
|
44
|
+
module ForMiniTest
|
45
|
+
def run(runner)
|
46
|
+
@runner = runner
|
47
|
+
|
48
|
+
run_warmup
|
49
|
+
if full_profile_options && metrics = full_profile_options[:metrics]
|
50
|
+
metrics.each do |metric_name|
|
51
|
+
if klass = Metrics[metric_name.to_sym]
|
52
|
+
run_profile(klass.new)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
return
|
34
58
|
end
|
35
59
|
|
60
|
+
def run_test(metric, mode)
|
61
|
+
result = '.'
|
62
|
+
begin
|
63
|
+
run_callbacks :setup
|
64
|
+
setup
|
65
|
+
metric.send(mode) { __send__ method_name }
|
66
|
+
rescue Exception => e
|
67
|
+
result = @runner.puke(self.class, method_name, e)
|
68
|
+
ensure
|
69
|
+
begin
|
70
|
+
teardown
|
71
|
+
run_callbacks :teardown, :enumerator => :reverse_each
|
72
|
+
rescue Exception => e
|
73
|
+
result = @runner.puke(self.class, method_name, e)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
result
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module ForClassicTestUnit
|
36
81
|
def run(result)
|
37
82
|
return if method_name =~ /^default_test$/
|
38
83
|
|
@@ -40,11 +85,13 @@ begin
|
|
40
85
|
@_result = result
|
41
86
|
|
42
87
|
run_warmup
|
43
|
-
if
|
88
|
+
if full_profile_options && metrics = full_profile_options[:metrics]
|
44
89
|
metrics.each do |metric_name|
|
45
90
|
if klass = Metrics[metric_name.to_sym]
|
46
91
|
run_profile(klass.new)
|
47
92
|
result.add_run
|
93
|
+
else
|
94
|
+
puts '%20s: unsupported' % metric_name
|
48
95
|
end
|
49
96
|
end
|
50
97
|
end
|
@@ -57,7 +104,7 @@ begin
|
|
57
104
|
setup
|
58
105
|
metric.send(mode) { __send__ @method_name }
|
59
106
|
rescue ::Test::Unit::AssertionFailedError => e
|
60
|
-
|
107
|
+
add_failure(e.message, e.backtrace)
|
61
108
|
rescue StandardError, ScriptError => e
|
62
109
|
add_error(e)
|
63
110
|
ensure
|
@@ -70,356 +117,201 @@ begin
|
|
70
117
|
add_error(e)
|
71
118
|
end
|
72
119
|
end
|
120
|
+
end
|
73
121
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
puts "%s (%s warmup)" % [full_test_name, time.format(time.total)]
|
81
|
-
|
82
|
-
GC.start
|
83
|
-
end
|
122
|
+
protected
|
123
|
+
# overridden by each implementation
|
124
|
+
def run_gc; end
|
125
|
+
|
126
|
+
def run_warmup
|
127
|
+
run_gc
|
84
128
|
|
85
|
-
|
86
|
-
|
87
|
-
|
129
|
+
time = Metrics::Time.new
|
130
|
+
run_test(time, :benchmark)
|
131
|
+
puts "%s (%s warmup)" % [full_test_name, time.format(time.total)]
|
88
132
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
133
|
+
run_gc
|
134
|
+
end
|
135
|
+
|
136
|
+
def run_profile(metric)
|
137
|
+
klass = full_profile_options[:benchmark] ? Benchmarker : Profiler
|
138
|
+
performer = klass.new(self, metric)
|
139
|
+
|
140
|
+
performer.run
|
141
|
+
puts performer.report
|
142
|
+
performer.record
|
143
|
+
end
|
93
144
|
|
94
|
-
|
95
|
-
|
145
|
+
class Performer
|
146
|
+
delegate :run_test, :full_profile_options, :full_test_name, :to => :@harness
|
96
147
|
|
97
|
-
|
98
|
-
|
99
|
-
|
148
|
+
def initialize(harness, metric)
|
149
|
+
@harness, @metric, @supported = harness, metric, false
|
150
|
+
end
|
100
151
|
|
101
|
-
|
102
|
-
|
152
|
+
def report
|
153
|
+
if @supported
|
154
|
+
rate = @total / full_profile_options[:runs]
|
103
155
|
'%20s: %s' % [@metric.name, @metric.format(rate)]
|
156
|
+
else
|
157
|
+
'%20s: unsupported' % @metric.name
|
104
158
|
end
|
105
|
-
|
106
|
-
protected
|
107
|
-
def output_filename
|
108
|
-
"#{profile_options[:output]}/#{full_test_name}_#{@metric.name}"
|
109
|
-
end
|
110
159
|
end
|
111
160
|
|
112
|
-
|
113
|
-
def
|
114
|
-
|
115
|
-
@total = @metric.total
|
116
|
-
end
|
117
|
-
|
118
|
-
def record
|
119
|
-
avg = @metric.total / profile_options[:runs].to_i
|
120
|
-
now = Time.now.utc.xmlschema
|
121
|
-
with_output_file do |file|
|
122
|
-
file.puts "#{avg},#{now},#{environment}"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def environment
|
127
|
-
unless defined? @env
|
128
|
-
app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
|
129
|
-
|
130
|
-
rails = Rails::VERSION::STRING
|
131
|
-
if File.directory?('vendor/rails/.git')
|
132
|
-
Dir.chdir('vendor/rails') do
|
133
|
-
rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
|
138
|
-
ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
|
139
|
-
|
140
|
-
@env = [app, rails, ruby, RUBY_PLATFORM] * ','
|
141
|
-
end
|
142
|
-
|
143
|
-
@env
|
161
|
+
protected
|
162
|
+
def output_filename
|
163
|
+
"#{full_profile_options[:output]}/#{full_test_name}_#{@metric.name}"
|
144
164
|
end
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
FileUtils.mkdir_p(File.dirname(fname))
|
154
|
-
end
|
155
|
-
|
156
|
-
File.open(fname, 'ab') do |file|
|
157
|
-
file.puts(HEADER) if new
|
158
|
-
yield file
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def output_filename
|
163
|
-
"#{super}.csv"
|
164
|
-
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# overridden by each implementation
|
168
|
+
class Profiler < Performer
|
169
|
+
def time_with_block
|
170
|
+
before = Time.now
|
171
|
+
yield
|
172
|
+
Time.now - before
|
165
173
|
end
|
174
|
+
|
175
|
+
def run; end
|
176
|
+
def record; end
|
177
|
+
end
|
166
178
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
profile_options[:runs].to_i.times { run_test(@metric, :profile) }
|
180
|
-
@data = RubyProf.stop
|
181
|
-
@total = @data.threads.values.sum(0) { |method_infos| method_infos.max.total_time }
|
182
|
-
end
|
179
|
+
class Benchmarker < Performer
|
180
|
+
def initialize(*args)
|
181
|
+
super
|
182
|
+
@supported = @metric.respond_to?('measure')
|
183
|
+
end
|
184
|
+
|
185
|
+
def run
|
186
|
+
return unless @supported
|
187
|
+
|
188
|
+
full_profile_options[:runs].to_i.times { run_test(@metric, :benchmark) }
|
189
|
+
@total = @metric.total
|
190
|
+
end
|
183
191
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
end
|
192
|
+
def record
|
193
|
+
avg = @metric.total / full_profile_options[:runs].to_i
|
194
|
+
now = Time.now.utc.xmlschema
|
195
|
+
with_output_file do |file|
|
196
|
+
file.puts "#{avg},#{now},#{environment}"
|
190
197
|
end
|
198
|
+
end
|
191
199
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact
|
200
|
+
def environment
|
201
|
+
unless defined? @env
|
202
|
+
app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/
|
196
203
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
klass.new(@data).print(file, profile_options.slice(:min_percent))
|
204
|
+
rails = Rails::VERSION::STRING
|
205
|
+
if File.directory?('vendor/rails/.git')
|
206
|
+
Dir.chdir('vendor/rails') do
|
207
|
+
rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/
|
202
208
|
end
|
203
209
|
end
|
204
|
-
end
|
205
210
|
|
206
|
-
|
207
|
-
|
208
|
-
suffix =
|
209
|
-
case printer_class.name.demodulize
|
210
|
-
when 'FlatPrinter'; 'flat.txt'
|
211
|
-
when 'FlatPrinterWithLineNumbers'; 'flat_line_numbers.txt'
|
212
|
-
when 'GraphPrinter'; 'graph.txt'
|
213
|
-
when 'GraphHtmlPrinter'; 'graph.html'
|
214
|
-
when 'GraphYamlPrinter'; 'graph.yml'
|
215
|
-
when 'CallTreePrinter'; 'tree.txt'
|
216
|
-
when 'CallStackPrinter'; 'stack.html'
|
217
|
-
when 'DotPrinter'; 'graph.dot'
|
218
|
-
else printer_class.name.sub(/Printer$/, '').underscore
|
219
|
-
end
|
220
|
-
|
221
|
-
"#{super()}_#{suffix}"
|
222
|
-
end
|
223
|
-
end
|
211
|
+
ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
|
212
|
+
ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}"
|
224
213
|
|
225
|
-
|
226
|
-
def self.[](name)
|
227
|
-
const_get(name.to_s.camelize)
|
228
|
-
rescue NameError
|
229
|
-
nil
|
214
|
+
@env = [app, rails, ruby, RUBY_PLATFORM] * ','
|
230
215
|
end
|
231
216
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
def initialize
|
236
|
-
@total = 0
|
237
|
-
end
|
238
|
-
|
239
|
-
def name
|
240
|
-
@name ||= self.class.name.demodulize.underscore
|
241
|
-
end
|
217
|
+
@env
|
218
|
+
end
|
242
219
|
|
243
|
-
|
244
|
-
|
245
|
-
end
|
220
|
+
protected
|
221
|
+
HEADER = 'measurement,created_at,app,rails,ruby,platform'
|
246
222
|
|
247
|
-
|
248
|
-
|
249
|
-
end
|
223
|
+
def with_output_file
|
224
|
+
fname = output_filename
|
250
225
|
|
251
|
-
|
252
|
-
|
253
|
-
before = measure
|
254
|
-
yield
|
255
|
-
@total += (measure - before)
|
256
|
-
end
|
226
|
+
if new = !File.exist?(fname)
|
227
|
+
FileUtils.mkdir_p(File.dirname(fname))
|
257
228
|
end
|
258
229
|
|
259
|
-
|
260
|
-
|
261
|
-
yield
|
262
|
-
ensure
|
263
|
-
RubyProf.pause
|
230
|
+
File.open(fname, 'ab') do |file|
|
231
|
+
file.puts(HEADER) if new
|
232
|
+
yield file
|
264
233
|
end
|
265
|
-
|
266
|
-
protected
|
267
|
-
# Ruby 1.9 with GC::Profiler
|
268
|
-
if defined?(GC::Profiler)
|
269
|
-
def with_gc_stats
|
270
|
-
GC::Profiler.enable
|
271
|
-
GC.start
|
272
|
-
yield
|
273
|
-
ensure
|
274
|
-
GC::Profiler.disable
|
275
|
-
end
|
276
|
-
|
277
|
-
# Ruby 1.8 + ruby-prof wrapper (enable/disable stats for Benchmarker)
|
278
|
-
elsif GC.respond_to?(:enable_stats)
|
279
|
-
def with_gc_stats
|
280
|
-
GC.enable_stats
|
281
|
-
yield
|
282
|
-
ensure
|
283
|
-
GC.disable_stats
|
284
|
-
end
|
285
|
-
|
286
|
-
else
|
287
|
-
def with_gc_stats
|
288
|
-
yield
|
289
|
-
end
|
290
|
-
end
|
291
234
|
end
|
292
235
|
|
293
|
-
|
294
|
-
|
295
|
-
::Time.now.to_f
|
296
|
-
end
|
297
|
-
|
298
|
-
def format(measurement)
|
299
|
-
if measurement < 1
|
300
|
-
'%d ms' % (measurement * 1000)
|
301
|
-
else
|
302
|
-
'%.2f sec' % measurement
|
303
|
-
end
|
304
|
-
end
|
236
|
+
def output_filename
|
237
|
+
"#{super}.csv"
|
305
238
|
end
|
239
|
+
end
|
240
|
+
|
241
|
+
module Metrics
|
242
|
+
def self.[](name)
|
243
|
+
const_get(name.to_s.camelize)
|
244
|
+
rescue NameError
|
245
|
+
nil
|
246
|
+
end
|
306
247
|
|
307
|
-
|
308
|
-
|
248
|
+
class Base
|
249
|
+
include ActionView::Helpers::NumberHelper
|
250
|
+
|
251
|
+
attr_reader :total
|
309
252
|
|
310
|
-
|
311
|
-
|
312
|
-
end
|
253
|
+
def initialize
|
254
|
+
@total = 0
|
313
255
|
end
|
314
256
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
def measure
|
319
|
-
RubyProf.measure_wall_time
|
320
|
-
end
|
257
|
+
def name
|
258
|
+
@name ||= self.class.name.demodulize.underscore
|
321
259
|
end
|
322
260
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
RubyProf.cpu_frequency = 2.33e9 unless RubyProf.cpu_frequency > 0
|
329
|
-
super
|
330
|
-
end
|
331
|
-
|
332
|
-
def measure
|
333
|
-
RubyProf.measure_cpu_time
|
261
|
+
def benchmark
|
262
|
+
with_gc_stats do
|
263
|
+
before = measure
|
264
|
+
yield
|
265
|
+
@total += (measure - before)
|
334
266
|
end
|
335
267
|
end
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
def measure
|
349
|
-
RubyProf.measure_memory / 1024.0
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
def format(measurement)
|
354
|
-
'%.2f KB' % measurement
|
355
|
-
end
|
268
|
+
|
269
|
+
# overridden by each implementation
|
270
|
+
def profile; end
|
271
|
+
|
272
|
+
protected
|
273
|
+
# overridden by each implementation
|
274
|
+
def with_gc_stats; end
|
275
|
+
end
|
276
|
+
|
277
|
+
class Time < Base
|
278
|
+
def measure
|
279
|
+
::Time.now.to_f
|
356
280
|
end
|
357
281
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
def measure
|
364
|
-
GC.malloc_allocations
|
365
|
-
end
|
366
|
-
|
367
|
-
# Ruby 1.8 + ruby-prof wrapper
|
368
|
-
elsif RubyProf.respond_to?(:measure_allocations)
|
369
|
-
def measure
|
370
|
-
RubyProf.measure_allocations
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
|
-
def format(measurement)
|
375
|
-
measurement.to_i.to_s
|
282
|
+
def format(measurement)
|
283
|
+
if measurement < 1
|
284
|
+
'%d ms' % (measurement * 1000)
|
285
|
+
else
|
286
|
+
'%.2f sec' % measurement
|
376
287
|
end
|
377
288
|
end
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
if GC.respond_to?(:count)
|
384
|
-
def measure
|
385
|
-
GC.count
|
386
|
-
end
|
387
|
-
|
388
|
-
# Ruby 1.8 + ruby-prof wrapper
|
389
|
-
elsif RubyProf.respond_to?(:measure_gc_runs)
|
390
|
-
def measure
|
391
|
-
RubyProf.measure_gc_runs
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
|
-
def format(measurement)
|
396
|
-
measurement.to_i.to_s
|
397
|
-
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class Amount < Base
|
292
|
+
def format(measurement)
|
293
|
+
number_with_delimiter(measurement.floor)
|
398
294
|
end
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
if defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time)
|
405
|
-
def measure
|
406
|
-
GC::Profiler.total_time
|
407
|
-
end
|
408
|
-
|
409
|
-
# Ruby 1.8 + ruby-prof wrapper
|
410
|
-
elsif RubyProf.respond_to?(:measure_gc_time)
|
411
|
-
def measure
|
412
|
-
RubyProf.measure_gc_time / 1000
|
413
|
-
end
|
414
|
-
end
|
415
|
-
|
416
|
-
def format(measurement)
|
417
|
-
'%.2f ms' % measurement
|
418
|
-
end
|
295
|
+
end
|
296
|
+
|
297
|
+
class DigitalInformationUnit < Base
|
298
|
+
def format(measurement)
|
299
|
+
number_to_human_size(measurement, :precision => 2)
|
419
300
|
end
|
420
301
|
end
|
302
|
+
|
303
|
+
# each implementation provides its own metrics like ProcessTime, Memory or GcRuns
|
421
304
|
end
|
422
305
|
end
|
423
306
|
end
|
424
|
-
|
307
|
+
end
|
308
|
+
|
309
|
+
RUBY_ENGINE = 'ruby' unless defined?(RUBY_ENGINE) # mri 1.8
|
310
|
+
case RUBY_ENGINE
|
311
|
+
when 'ruby' then require 'active_support/testing/performance/ruby'
|
312
|
+
when 'rbx' then require 'active_support/testing/performance/rubinius'
|
313
|
+
when 'jruby' then require 'active_support/testing/performance/jruby'
|
314
|
+
else
|
315
|
+
$stderr.puts 'Your ruby interpreter is not supported for benchmarking.'
|
316
|
+
exit
|
425
317
|
end
|