core_ext 0.0.1
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.
- checksums.yaml +7 -0
- data/README.md +3 -0
- data/lib/core_ext/array/access.rb +76 -0
- data/lib/core_ext/array/conversions.rb +211 -0
- data/lib/core_ext/array/extract_options.rb +29 -0
- data/lib/core_ext/array/grouping.rb +116 -0
- data/lib/core_ext/array/inquiry.rb +17 -0
- data/lib/core_ext/array/prepend_and_append.rb +7 -0
- data/lib/core_ext/array/wrap.rb +46 -0
- data/lib/core_ext/array.rb +7 -0
- data/lib/core_ext/array_inquirer.rb +44 -0
- data/lib/core_ext/benchmark.rb +14 -0
- data/lib/core_ext/benchmarkable.rb +49 -0
- data/lib/core_ext/big_decimal/conversions.rb +14 -0
- data/lib/core_ext/big_decimal.rb +1 -0
- data/lib/core_ext/builder.rb +6 -0
- data/lib/core_ext/callbacks.rb +770 -0
- data/lib/core_ext/class/attribute.rb +128 -0
- data/lib/core_ext/class/attribute_accessors.rb +4 -0
- data/lib/core_ext/class/subclasses.rb +42 -0
- data/lib/core_ext/class.rb +2 -0
- data/lib/core_ext/concern.rb +142 -0
- data/lib/core_ext/configurable.rb +148 -0
- data/lib/core_ext/date/acts_like.rb +8 -0
- data/lib/core_ext/date/blank.rb +12 -0
- data/lib/core_ext/date/calculations.rb +143 -0
- data/lib/core_ext/date/conversions.rb +93 -0
- data/lib/core_ext/date/zones.rb +6 -0
- data/lib/core_ext/date.rb +5 -0
- data/lib/core_ext/date_and_time/calculations.rb +328 -0
- data/lib/core_ext/date_and_time/zones.rb +40 -0
- data/lib/core_ext/date_time/acts_like.rb +14 -0
- data/lib/core_ext/date_time/blank.rb +12 -0
- data/lib/core_ext/date_time/calculations.rb +177 -0
- data/lib/core_ext/date_time/conversions.rb +104 -0
- data/lib/core_ext/date_time/zones.rb +6 -0
- data/lib/core_ext/date_time.rb +5 -0
- data/lib/core_ext/deprecation/behaviors.rb +86 -0
- data/lib/core_ext/deprecation/instance_delegator.rb +24 -0
- data/lib/core_ext/deprecation/method_wrappers.rb +70 -0
- data/lib/core_ext/deprecation/proxy_wrappers.rb +149 -0
- data/lib/core_ext/deprecation/reporting.rb +105 -0
- data/lib/core_ext/deprecation.rb +43 -0
- data/lib/core_ext/digest/uuid.rb +51 -0
- data/lib/core_ext/duration.rb +157 -0
- data/lib/core_ext/enumerable.rb +106 -0
- data/lib/core_ext/file/atomic.rb +68 -0
- data/lib/core_ext/file.rb +1 -0
- data/lib/core_ext/hash/compact.rb +20 -0
- data/lib/core_ext/hash/conversions.rb +261 -0
- data/lib/core_ext/hash/deep_merge.rb +38 -0
- data/lib/core_ext/hash/except.rb +22 -0
- data/lib/core_ext/hash/indifferent_access.rb +23 -0
- data/lib/core_ext/hash/keys.rb +170 -0
- data/lib/core_ext/hash/reverse_merge.rb +22 -0
- data/lib/core_ext/hash/slice.rb +48 -0
- data/lib/core_ext/hash/transform_values.rb +29 -0
- data/lib/core_ext/hash.rb +9 -0
- data/lib/core_ext/hash_with_indifferent_access.rb +298 -0
- data/lib/core_ext/inflections.rb +70 -0
- data/lib/core_ext/inflector/inflections.rb +244 -0
- data/lib/core_ext/inflector/methods.rb +381 -0
- data/lib/core_ext/inflector/transliterate.rb +112 -0
- data/lib/core_ext/inflector.rb +7 -0
- data/lib/core_ext/integer/inflections.rb +29 -0
- data/lib/core_ext/integer/multiple.rb +10 -0
- data/lib/core_ext/integer/time.rb +29 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/json/decoding.rb +67 -0
- data/lib/core_ext/json/encoding.rb +127 -0
- data/lib/core_ext/json.rb +2 -0
- data/lib/core_ext/kernel/agnostics.rb +11 -0
- data/lib/core_ext/kernel/concern.rb +10 -0
- data/lib/core_ext/kernel/reporting.rb +41 -0
- data/lib/core_ext/kernel/singleton_class.rb +6 -0
- data/lib/core_ext/kernel.rb +4 -0
- data/lib/core_ext/load_error.rb +30 -0
- data/lib/core_ext/logger.rb +57 -0
- data/lib/core_ext/logger_silence.rb +24 -0
- data/lib/core_ext/marshal.rb +19 -0
- data/lib/core_ext/module/aliasing.rb +74 -0
- data/lib/core_ext/module/anonymous.rb +28 -0
- data/lib/core_ext/module/attr_internal.rb +36 -0
- data/lib/core_ext/module/attribute_accessors.rb +212 -0
- data/lib/core_ext/module/concerning.rb +135 -0
- data/lib/core_ext/module/delegation.rb +218 -0
- data/lib/core_ext/module/deprecation.rb +23 -0
- data/lib/core_ext/module/introspection.rb +62 -0
- data/lib/core_ext/module/method_transplanting.rb +3 -0
- data/lib/core_ext/module/qualified_const.rb +52 -0
- data/lib/core_ext/module/reachable.rb +8 -0
- data/lib/core_ext/module/remove_method.rb +35 -0
- data/lib/core_ext/module.rb +11 -0
- data/lib/core_ext/multibyte/chars.rb +231 -0
- data/lib/core_ext/multibyte/unicode.rb +388 -0
- data/lib/core_ext/multibyte.rb +21 -0
- data/lib/core_ext/name_error.rb +31 -0
- data/lib/core_ext/numeric/bytes.rb +64 -0
- data/lib/core_ext/numeric/conversions.rb +132 -0
- data/lib/core_ext/numeric/inquiry.rb +26 -0
- data/lib/core_ext/numeric/time.rb +74 -0
- data/lib/core_ext/numeric.rb +4 -0
- data/lib/core_ext/object/acts_like.rb +10 -0
- data/lib/core_ext/object/blank.rb +140 -0
- data/lib/core_ext/object/conversions.rb +4 -0
- data/lib/core_ext/object/deep_dup.rb +53 -0
- data/lib/core_ext/object/duplicable.rb +98 -0
- data/lib/core_ext/object/inclusion.rb +27 -0
- data/lib/core_ext/object/instance_variables.rb +28 -0
- data/lib/core_ext/object/json.rb +199 -0
- data/lib/core_ext/object/to_param.rb +1 -0
- data/lib/core_ext/object/to_query.rb +84 -0
- data/lib/core_ext/object/try.rb +146 -0
- data/lib/core_ext/object/with_options.rb +69 -0
- data/lib/core_ext/object.rb +14 -0
- data/lib/core_ext/option_merger.rb +25 -0
- data/lib/core_ext/ordered_hash.rb +48 -0
- data/lib/core_ext/ordered_options.rb +81 -0
- data/lib/core_ext/range/conversions.rb +34 -0
- data/lib/core_ext/range/each.rb +21 -0
- data/lib/core_ext/range/include_range.rb +23 -0
- data/lib/core_ext/range/overlaps.rb +8 -0
- data/lib/core_ext/range.rb +4 -0
- data/lib/core_ext/regexp.rb +5 -0
- data/lib/core_ext/rescuable.rb +119 -0
- data/lib/core_ext/securerandom.rb +23 -0
- data/lib/core_ext/security_utils.rb +20 -0
- data/lib/core_ext/string/access.rb +104 -0
- data/lib/core_ext/string/behavior.rb +6 -0
- data/lib/core_ext/string/conversions.rb +56 -0
- data/lib/core_ext/string/exclude.rb +11 -0
- data/lib/core_ext/string/filters.rb +102 -0
- data/lib/core_ext/string/indent.rb +43 -0
- data/lib/core_ext/string/inflections.rb +235 -0
- data/lib/core_ext/string/inquiry.rb +13 -0
- data/lib/core_ext/string/multibyte.rb +53 -0
- data/lib/core_ext/string/output_safety.rb +261 -0
- data/lib/core_ext/string/starts_ends_with.rb +4 -0
- data/lib/core_ext/string/strip.rb +23 -0
- data/lib/core_ext/string/zones.rb +14 -0
- data/lib/core_ext/string.rb +13 -0
- data/lib/core_ext/string_inquirer.rb +26 -0
- data/lib/core_ext/tagged_logging.rb +78 -0
- data/lib/core_ext/test_case.rb +88 -0
- data/lib/core_ext/testing/assertions.rb +99 -0
- data/lib/core_ext/testing/autorun.rb +12 -0
- data/lib/core_ext/testing/composite_filter.rb +54 -0
- data/lib/core_ext/testing/constant_lookup.rb +50 -0
- data/lib/core_ext/testing/declarative.rb +26 -0
- data/lib/core_ext/testing/deprecation.rb +36 -0
- data/lib/core_ext/testing/file_fixtures.rb +34 -0
- data/lib/core_ext/testing/isolation.rb +115 -0
- data/lib/core_ext/testing/method_call_assertions.rb +41 -0
- data/lib/core_ext/testing/setup_and_teardown.rb +50 -0
- data/lib/core_ext/testing/stream.rb +42 -0
- data/lib/core_ext/testing/tagged_logging.rb +25 -0
- data/lib/core_ext/testing/time_helpers.rb +134 -0
- data/lib/core_ext/time/acts_like.rb +8 -0
- data/lib/core_ext/time/calculations.rb +284 -0
- data/lib/core_ext/time/conversions.rb +66 -0
- data/lib/core_ext/time/zones.rb +95 -0
- data/lib/core_ext/time.rb +20 -0
- data/lib/core_ext/time_with_zone.rb +503 -0
- data/lib/core_ext/time_zone.rb +464 -0
- data/lib/core_ext/uri.rb +25 -0
- data/lib/core_ext/version.rb +3 -0
- data/lib/core_ext/xml_mini/jdom.rb +181 -0
- data/lib/core_ext/xml_mini/libxml.rb +79 -0
- data/lib/core_ext/xml_mini/libxmlsax.rb +85 -0
- data/lib/core_ext/xml_mini/nokogiri.rb +83 -0
- data/lib/core_ext/xml_mini/nokogirisax.rb +87 -0
- data/lib/core_ext/xml_mini/rexml.rb +130 -0
- data/lib/core_ext/xml_mini.rb +194 -0
- data/lib/core_ext.rb +3 -0
- metadata +310 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require "core_ext/concern"
|
|
2
|
+
require "core_ext/inflector"
|
|
3
|
+
|
|
4
|
+
module CoreExt
|
|
5
|
+
module Testing
|
|
6
|
+
# Resolves a constant from a minitest spec name.
|
|
7
|
+
#
|
|
8
|
+
# Given the following spec-style test:
|
|
9
|
+
#
|
|
10
|
+
# describe WidgetsController, :index do
|
|
11
|
+
# describe "authenticated user" do
|
|
12
|
+
# describe "returns widgets" do
|
|
13
|
+
# it "has a controller that exists" do
|
|
14
|
+
# assert_kind_of WidgetsController, @controller
|
|
15
|
+
# end
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# The test will have the following name:
|
|
21
|
+
#
|
|
22
|
+
# "WidgetsController::index::authenticated user::returns widgets"
|
|
23
|
+
#
|
|
24
|
+
# The constant WidgetsController can be resolved from the name.
|
|
25
|
+
# The following code will resolve the constant:
|
|
26
|
+
#
|
|
27
|
+
# controller = determine_constant_from_test_name(name) do |constant|
|
|
28
|
+
# Class === constant && constant < ::ActionController::Metal
|
|
29
|
+
# end
|
|
30
|
+
module ConstantLookup
|
|
31
|
+
extend ::CoreExt::Concern
|
|
32
|
+
|
|
33
|
+
module ClassMethods # :nodoc:
|
|
34
|
+
def determine_constant_from_test_name(test_name)
|
|
35
|
+
names = test_name.split "::"
|
|
36
|
+
while names.size > 0 do
|
|
37
|
+
names.last.sub!(/Test$/, "")
|
|
38
|
+
begin
|
|
39
|
+
constant = names.join("::").safe_constantize
|
|
40
|
+
break(constant) if yield(constant)
|
|
41
|
+
ensure
|
|
42
|
+
names.pop
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module CoreExt
|
|
2
|
+
module Testing
|
|
3
|
+
module Declarative
|
|
4
|
+
unless defined?(Spec)
|
|
5
|
+
# Helper to define a test method using a String. Under the hood, it replaces
|
|
6
|
+
# spaces with underscores and defines the test method.
|
|
7
|
+
#
|
|
8
|
+
# test "verify something" do
|
|
9
|
+
# ...
|
|
10
|
+
# end
|
|
11
|
+
def test(name, &block)
|
|
12
|
+
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
|
|
13
|
+
defined = method_defined? test_name
|
|
14
|
+
raise "#{test_name} is already defined in #{self}" if defined
|
|
15
|
+
if block_given?
|
|
16
|
+
define_method(test_name, &block)
|
|
17
|
+
else
|
|
18
|
+
define_method(test_name) do
|
|
19
|
+
flunk "No implementation provided for #{name}"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'core_ext/deprecation'
|
|
2
|
+
|
|
3
|
+
module CoreExt
|
|
4
|
+
module Testing
|
|
5
|
+
module Deprecation #:nodoc:
|
|
6
|
+
def assert_deprecated(match = nil, deprecator = nil, &block)
|
|
7
|
+
result, warnings = collect_deprecations(deprecator, &block)
|
|
8
|
+
assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
|
|
9
|
+
if match
|
|
10
|
+
match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
|
|
11
|
+
assert warnings.any? { |w| w =~ match }, "No deprecation warning matched #{match}: #{warnings.join(', ')}"
|
|
12
|
+
end
|
|
13
|
+
result
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def assert_not_deprecated(deprecator = nil, &block)
|
|
17
|
+
result, deprecations = collect_deprecations(deprecator, &block)
|
|
18
|
+
assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
|
|
19
|
+
result
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def collect_deprecations(deprecator = nil)
|
|
23
|
+
deprecator ||= CoreExt::Deprecation
|
|
24
|
+
old_behavior = deprecator.behavior
|
|
25
|
+
deprecations = []
|
|
26
|
+
deprecator.behavior = Proc.new do |message, callstack|
|
|
27
|
+
deprecations << message
|
|
28
|
+
end
|
|
29
|
+
result = yield
|
|
30
|
+
[result, deprecations]
|
|
31
|
+
ensure
|
|
32
|
+
deprecator.behavior = old_behavior
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module CoreExt
|
|
2
|
+
module Testing
|
|
3
|
+
# Adds simple access to sample files called file fixtures.
|
|
4
|
+
# File fixtures are normal files stored in
|
|
5
|
+
# <tt>CoreExt::TestCase.file_fixture_path</tt>.
|
|
6
|
+
#
|
|
7
|
+
# File fixtures are represented as +Pathname+ objects.
|
|
8
|
+
# This makes it easy to extract specific information:
|
|
9
|
+
#
|
|
10
|
+
# file_fixture("example.txt").read # get the file's content
|
|
11
|
+
# file_fixture("example.mp3").size # get the file size
|
|
12
|
+
module FileFixtures
|
|
13
|
+
extend CoreExt::Concern
|
|
14
|
+
|
|
15
|
+
included do
|
|
16
|
+
class_attribute :file_fixture_path, instance_writer: false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns a +Pathname+ to the fixture file named +fixture_name+.
|
|
20
|
+
#
|
|
21
|
+
# Raises +ArgumentError+ if +fixture_name+ can't be found.
|
|
22
|
+
def file_fixture(fixture_name)
|
|
23
|
+
path = Pathname.new(File.join(file_fixture_path, fixture_name))
|
|
24
|
+
|
|
25
|
+
if path.exist?
|
|
26
|
+
path
|
|
27
|
+
else
|
|
28
|
+
msg = "the directory '%s' does not contain a file named '%s'"
|
|
29
|
+
raise ArgumentError, msg % [file_fixture_path, fixture_name]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
module CoreExt
|
|
2
|
+
module Testing
|
|
3
|
+
module Isolation
|
|
4
|
+
require 'thread'
|
|
5
|
+
|
|
6
|
+
def self.included(klass) #:nodoc:
|
|
7
|
+
klass.class_eval do
|
|
8
|
+
parallelize_me!
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.forking_env?
|
|
13
|
+
!ENV["NO_FORK"] && Process.respond_to?(:fork)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@@class_setup_mutex = Mutex.new
|
|
17
|
+
|
|
18
|
+
def _run_class_setup # class setup method should only happen in parent
|
|
19
|
+
@@class_setup_mutex.synchronize do
|
|
20
|
+
unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST']
|
|
21
|
+
self.class.setup if self.class.respond_to?(:setup)
|
|
22
|
+
@@ran_class_setup = true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run
|
|
28
|
+
serialized = run_in_isolation do
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
Marshal.load(serialized)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module Forking
|
|
36
|
+
def run_in_isolation(&blk)
|
|
37
|
+
read, write = IO.pipe
|
|
38
|
+
read.binmode
|
|
39
|
+
write.binmode
|
|
40
|
+
|
|
41
|
+
pid = fork do
|
|
42
|
+
read.close
|
|
43
|
+
yield
|
|
44
|
+
begin
|
|
45
|
+
if error?
|
|
46
|
+
failures.map! { |e|
|
|
47
|
+
begin
|
|
48
|
+
Marshal.dump e
|
|
49
|
+
e
|
|
50
|
+
rescue TypeError
|
|
51
|
+
ex = Exception.new e.message
|
|
52
|
+
ex.set_backtrace e.backtrace
|
|
53
|
+
Minitest::UnexpectedError.new ex
|
|
54
|
+
end
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
result = Marshal.dump(self.dup)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
write.puts [result].pack("m")
|
|
61
|
+
exit!
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
write.close
|
|
65
|
+
result = read.read
|
|
66
|
+
Process.wait2(pid)
|
|
67
|
+
return result.unpack("m")[0]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
module Subprocess
|
|
72
|
+
ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV)
|
|
73
|
+
|
|
74
|
+
# Crazy H4X to get this working in windows / jruby with
|
|
75
|
+
# no forking.
|
|
76
|
+
def run_in_isolation(&blk)
|
|
77
|
+
require "tempfile"
|
|
78
|
+
|
|
79
|
+
if ENV["ISOLATION_TEST"]
|
|
80
|
+
yield
|
|
81
|
+
File.open(ENV["ISOLATION_OUTPUT"], "w") do |file|
|
|
82
|
+
file.puts [Marshal.dump(self.dup)].pack("m")
|
|
83
|
+
end
|
|
84
|
+
exit!
|
|
85
|
+
else
|
|
86
|
+
Tempfile.open("isolation") do |tmpfile|
|
|
87
|
+
env = {
|
|
88
|
+
'ISOLATION_TEST' => self.class.name,
|
|
89
|
+
'ISOLATION_OUTPUT' => tmpfile.path
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ")
|
|
93
|
+
orig_args = ORIG_ARGV.join(" ")
|
|
94
|
+
test_opts = "-n#{self.class.name}##{self.name}"
|
|
95
|
+
command = "#{Gem.ruby} #{load_paths} #{$0} '#{orig_args}' #{test_opts}"
|
|
96
|
+
|
|
97
|
+
# IO.popen lets us pass env in a cross-platform way
|
|
98
|
+
child = IO.popen(env, command)
|
|
99
|
+
|
|
100
|
+
begin
|
|
101
|
+
Process.wait(child.pid)
|
|
102
|
+
rescue Errno::ECHILD # The child process may exit before we wait
|
|
103
|
+
nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
return tmpfile.read.unpack("m")[0]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
include forking_env? ? Forking : Subprocess
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'minitest/mock'
|
|
2
|
+
|
|
3
|
+
module CoreExt
|
|
4
|
+
module Testing
|
|
5
|
+
module MethodCallAssertions # :nodoc:
|
|
6
|
+
private
|
|
7
|
+
def assert_called(object, method_name, message = nil, times: 1, returns: nil)
|
|
8
|
+
times_called = 0
|
|
9
|
+
|
|
10
|
+
object.stub(method_name, proc { times_called += 1; returns }) { yield }
|
|
11
|
+
|
|
12
|
+
error = "Expected #{method_name} to be called #{times} times, " \
|
|
13
|
+
"but was called #{times_called} times"
|
|
14
|
+
error = "#{message}.\n#{error}" if message
|
|
15
|
+
assert_equal times, times_called, error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def assert_called_with(object, method_name, args = [], returns: nil)
|
|
19
|
+
mock = Minitest::Mock.new
|
|
20
|
+
|
|
21
|
+
if args.all? { |arg| arg.is_a?(Array) }
|
|
22
|
+
args.each { |arg| mock.expect(:call, returns, arg) }
|
|
23
|
+
else
|
|
24
|
+
mock.expect(:call, returns, args)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
object.stub(method_name, mock) { yield }
|
|
28
|
+
|
|
29
|
+
mock.verify
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def assert_not_called(object, method_name, message = nil, &block)
|
|
33
|
+
assert_called(object, method_name, message, times: 0, &block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def stub_any_instance(klass, instance: klass.new)
|
|
37
|
+
klass.stub(:new, instance) { yield instance }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'core_ext/concern'
|
|
2
|
+
require 'core_ext/callbacks'
|
|
3
|
+
|
|
4
|
+
module CoreExt
|
|
5
|
+
module Testing
|
|
6
|
+
# Adds support for +setup+ and +teardown+ callbacks.
|
|
7
|
+
# These callbacks serve as a replacement to overwriting the
|
|
8
|
+
# <tt>#setup</tt> and <tt>#teardown</tt> methods of your TestCase.
|
|
9
|
+
#
|
|
10
|
+
# class ExampleTest < CoreExt::TestCase
|
|
11
|
+
# setup do
|
|
12
|
+
# # ...
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# teardown do
|
|
16
|
+
# # ...
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
module SetupAndTeardown
|
|
20
|
+
extend CoreExt::Concern
|
|
21
|
+
|
|
22
|
+
included do
|
|
23
|
+
include CoreExt::Callbacks
|
|
24
|
+
define_callbacks :setup, :teardown
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module ClassMethods
|
|
28
|
+
# Add a callback, which runs before <tt>TestCase#setup</tt>.
|
|
29
|
+
def setup(*args, &block)
|
|
30
|
+
set_callback(:setup, :before, *args, &block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Add a callback, which runs after <tt>TestCase#teardown</tt>.
|
|
34
|
+
def teardown(*args, &block)
|
|
35
|
+
set_callback(:teardown, :after, *args, &block)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def before_setup # :nodoc:
|
|
40
|
+
super
|
|
41
|
+
run_callbacks :setup
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def after_teardown # :nodoc:
|
|
45
|
+
run_callbacks :teardown
|
|
46
|
+
super
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module CoreExt
|
|
2
|
+
module Testing
|
|
3
|
+
module Stream #:nodoc:
|
|
4
|
+
private
|
|
5
|
+
|
|
6
|
+
def silence_stream(stream)
|
|
7
|
+
old_stream = stream.dup
|
|
8
|
+
stream.reopen(IO::NULL)
|
|
9
|
+
stream.sync = true
|
|
10
|
+
yield
|
|
11
|
+
ensure
|
|
12
|
+
stream.reopen(old_stream)
|
|
13
|
+
old_stream.close
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def quietly
|
|
17
|
+
silence_stream(STDOUT) do
|
|
18
|
+
silence_stream(STDERR) do
|
|
19
|
+
yield
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def capture(stream)
|
|
25
|
+
stream = stream.to_s
|
|
26
|
+
captured_stream = Tempfile.new(stream)
|
|
27
|
+
stream_io = eval("$#{stream}")
|
|
28
|
+
origin_stream = stream_io.dup
|
|
29
|
+
stream_io.reopen(captured_stream)
|
|
30
|
+
|
|
31
|
+
yield
|
|
32
|
+
|
|
33
|
+
stream_io.rewind
|
|
34
|
+
return captured_stream.read
|
|
35
|
+
ensure
|
|
36
|
+
captured_stream.close
|
|
37
|
+
captured_stream.unlink
|
|
38
|
+
stream_io.reopen(origin_stream)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module CoreExt
|
|
2
|
+
module Testing
|
|
3
|
+
# Logs a "PostsControllerTest: test name" heading before each test to
|
|
4
|
+
# make test.log easier to search and follow along with.
|
|
5
|
+
module TaggedLogging #:nodoc:
|
|
6
|
+
attr_writer :tagged_logger
|
|
7
|
+
|
|
8
|
+
def before_setup
|
|
9
|
+
if tagged_logger && tagged_logger.info?
|
|
10
|
+
heading = "#{self.class}: #{name}"
|
|
11
|
+
divider = '-' * heading.size
|
|
12
|
+
tagged_logger.info divider
|
|
13
|
+
tagged_logger.info heading
|
|
14
|
+
tagged_logger.info divider
|
|
15
|
+
end
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
def tagged_logger
|
|
21
|
+
@tagged_logger ||= (defined?(Rails.logger) && Rails.logger)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
module CoreExt
|
|
2
|
+
module Testing
|
|
3
|
+
class SimpleStubs # :nodoc:
|
|
4
|
+
Stub = Struct.new(:object, :method_name, :original_method)
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@stubs = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def stub_object(object, method_name, return_value)
|
|
11
|
+
key = [object.object_id, method_name]
|
|
12
|
+
|
|
13
|
+
if stub = @stubs[key]
|
|
14
|
+
unstub_object(stub)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
new_name = "__simple_stub__#{method_name}"
|
|
18
|
+
|
|
19
|
+
@stubs[key] = Stub.new(object, method_name, new_name)
|
|
20
|
+
|
|
21
|
+
object.singleton_class.send :alias_method, new_name, method_name
|
|
22
|
+
object.define_singleton_method(method_name) { return_value }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def unstub_all!
|
|
26
|
+
@stubs.each_value do |stub|
|
|
27
|
+
unstub_object(stub)
|
|
28
|
+
end
|
|
29
|
+
@stubs = {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def unstub_object(stub)
|
|
35
|
+
singleton_class = stub.object.singleton_class
|
|
36
|
+
singleton_class.send :undef_method, stub.method_name
|
|
37
|
+
singleton_class.send :alias_method, stub.method_name, stub.original_method
|
|
38
|
+
singleton_class.send :undef_method, stub.original_method
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Contains helpers that help you test passage of time.
|
|
43
|
+
module TimeHelpers
|
|
44
|
+
# Changes current time to the time in the future or in the past by a given time difference by
|
|
45
|
+
# stubbing +Time.now+, +Date.today+, and +DateTime.now+.
|
|
46
|
+
#
|
|
47
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
|
48
|
+
# travel 1.day
|
|
49
|
+
# Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00
|
|
50
|
+
# Date.current # => Sun, 10 Nov 2013
|
|
51
|
+
# DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500
|
|
52
|
+
#
|
|
53
|
+
# This method also accepts a block, which will return the current time back to its original
|
|
54
|
+
# state at the end of the block:
|
|
55
|
+
#
|
|
56
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
|
57
|
+
# travel 1.day do
|
|
58
|
+
# User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
|
|
59
|
+
# end
|
|
60
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
|
61
|
+
def travel(duration, &block)
|
|
62
|
+
travel_to Time.now + duration, &block
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Changes current time to the given time by stubbing +Time.now+,
|
|
66
|
+
# +Date.today+, and +DateTime.now+ to return the time or date passed into this method.
|
|
67
|
+
#
|
|
68
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
|
69
|
+
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
|
|
70
|
+
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
|
|
71
|
+
# Date.current # => Wed, 24 Nov 2004
|
|
72
|
+
# DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500
|
|
73
|
+
#
|
|
74
|
+
# Dates are taken as their timestamp at the beginning of the day in the
|
|
75
|
+
# application time zone. <tt>Time.current</tt> returns said timestamp,
|
|
76
|
+
# and <tt>Time.now</tt> its equivalent in the system time zone. Similarly,
|
|
77
|
+
# <tt>Date.current</tt> returns a date equal to the argument, and
|
|
78
|
+
# <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may
|
|
79
|
+
# be different. (Note that you rarely want to deal with <tt>Time.now</tt>,
|
|
80
|
+
# or <tt>Date.today</tt>, in order to honor the application time zone
|
|
81
|
+
# please always use <tt>Time.current</tt> and <tt>Date.current</tt>.)
|
|
82
|
+
#
|
|
83
|
+
# Note that the usec for the time passed will be set to 0 to prevent rounding
|
|
84
|
+
# errors with external services, like MySQL (which will round instead of floor,
|
|
85
|
+
# leading to off-by-one-second errors).
|
|
86
|
+
#
|
|
87
|
+
# This method also accepts a block, which will return the current time back to its original
|
|
88
|
+
# state at the end of the block:
|
|
89
|
+
#
|
|
90
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
|
91
|
+
# travel_to Time.new(2004, 11, 24, 01, 04, 44) do
|
|
92
|
+
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
|
|
93
|
+
# end
|
|
94
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
|
95
|
+
def travel_to(date_or_time)
|
|
96
|
+
if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
|
|
97
|
+
now = date_or_time.midnight.to_time
|
|
98
|
+
else
|
|
99
|
+
now = date_or_time.to_time.change(usec: 0)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
simple_stubs.stub_object(Time, :now, now)
|
|
103
|
+
simple_stubs.stub_object(Date, :today, now.to_date)
|
|
104
|
+
simple_stubs.stub_object(DateTime, :now, now.to_datetime)
|
|
105
|
+
|
|
106
|
+
if block_given?
|
|
107
|
+
begin
|
|
108
|
+
yield
|
|
109
|
+
ensure
|
|
110
|
+
travel_back
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns the current time back to its original state, by removing the stubs added by
|
|
116
|
+
# `travel` and `travel_to`.
|
|
117
|
+
#
|
|
118
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
|
119
|
+
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
|
|
120
|
+
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
|
|
121
|
+
# travel_back
|
|
122
|
+
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
|
|
123
|
+
def travel_back
|
|
124
|
+
simple_stubs.unstub_all!
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def simple_stubs
|
|
130
|
+
@simple_stubs ||= SimpleStubs.new
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|