diff --git a/test/support/mspec.rb b/test/support/mspec.rb index 0e5f21b67e..b1495b69c8 100644 --- a/test/support/mspec.rb +++ b/test/support/mspec.rb @@ -1,4 +1,4 @@ -class MSpec +module MSpec def self.features @features || {} end diff --git a/test/support/spec.rb b/test/support/spec.rb index 378f673ab1..f88b5f7f36 100644 --- a/test/support/spec.rb +++ b/test/support/spec.rb @@ -7,8 +7,10 @@ require_relative 'version' require_relative 'spec_helpers/fs' require_relative 'spec_helpers/io' +require_relative 'spec_helpers/matchers' require_relative 'spec_helpers/mock_to_path' require_relative 'spec_helpers/tmp' +require_relative 'spec_utils/format' require_relative 'spec_utils/warnings' require_relative 'nat_binary' require 'tempfile' @@ -20,9 +22,6 @@ class NatalieFixMeException < SpecFailedException class UnknownFormatterException < StandardError end -TOLERANCE = 0.00003 -TIME_TOLERANCE = 20.0 - FORMATTERS = %w[default yaml spec] @formatter_name = ARGV[ARGV.index('-f') + 1] if ARGV.include?('-f') @@ -543,333 +542,20 @@ def method_missing(method, *args) undef_method :equal? end -class BeNilExpectation - def match(subject) - raise SpecFailedException, subject.inspect + ' should be nil' if subject != nil - end - - def inverted_match(subject) - raise SpecFailedException, subject.inspect + ' should not be nil' if subject == nil - end -end - -class BeKindOfExpectation - def initialize(klass) - @klass = klass - end - - def match(subject) - raise SpecFailedException, "#{subject.inspect} (#{subject.class}) should be a kind of #{@klass}" if !(@klass === subject) - end - - def inverted_match(subject) - raise SpecFailedException, "#{subject.inspect} (#{subject.class}) should not be a kind of #{@klass}" if @klass === subject - end -end - -class BeInstanceOfExpectation - def initialize(klass) - @klass = klass - end - - def match(subject) - raise SpecFailedException, "#{subject.inspect} (#{subject.class}) should be an instance of #{@klass}" if !subject.instance_of?(@klass) - end - - def inverted_match(subject) - raise SpecFailedException, "#{subject.inspect} (#{subject.class}) should not be an instance of #{@klass}" if subject.instance_of?(@klass) - end -end - -class BeAncestorOfExpectation - def initialize(klass) - @klass = klass - end - - def match(subject) - unless @klass.ancestors.include?(subject) - raise SpecFailedException, subject.inspect + ' should be an ancestor of ' + @klass.inspect - end - end - - def inverted_match(subject) - if @klass.ancestors.include?(subject) - raise SpecFailedException, subject.inspect + ' should not to be an ancestor of ' + @klass.inspect - end - end -end - -class BlockCallerExpectation - def match(subject) - unless check(subject) - raise SpecFailedException, subject.inspect + ' should have blocked, but it did not' - end - end - - def inverted_match(subject) - if check(subject) - raise SpecFailedException, subject.inspect + ' should have not have blocked, but it did' - end - end - - private - - # I borrowed this from https://github.com/ruby/mspec/blob/master/lib/mspec/matchers/block_caller.rb - # Copyright (c) 2008 Engine Yard, Inc. All rights reserved. - # Licensed Under the MIT license. - def check(subject) - t = Thread.new { subject.call } - - loop do - case t.status - when 'sleep' # blocked - t.kill - t.join - return true - when false # terminated normally, so never blocked - t.join - return false - when nil # terminated exceptionally - t.value - else - Thread.pass - end - end - end -end - -class EqlExpectation - def initialize(other) - @other = other - end - - def match(subject) - if !subject.eql?(@other) - raise SpecFailedException, subject.inspect + ' should be eql (same type) to ' + @other.inspect - end - end - - def inverted_match(subject) - if subject.eql?(@other) - raise SpecFailedException, subject.inspect + ' should not be eql (same type) to ' + @other.inspect - end - end -end - -class BeEmptyExpectation - def match(subject) - if (subject.length > 0) - raise SpecFailedException, subject.inspect + ' should be empty but has size ' + subject.length - end - end - - def inverted_match(subject) - raise SpecFailedException, subject.inspect + ' should not be empty' if (subject.length == 0) - end -end - -class EqualExpectation - def initialize(other) - @other = other - end - - def match(subject) - raise SpecFailedException, subject.inspect + ' should be equal to ' + @other.inspect if !subject.equal?(@other) - end - - def inverted_match(subject) - raise SpecFailedException, subject.inspect + ' should not be equal to ' + @other.inspect if subject.equal?(@other) - end -end - -# Largely taken from -# https://github.com/ruby/ruby/blob/master/spec/mspec/lib/mspec/matchers/equal_element.rb#L76 -class EqualElementExpectation - def initialize(element, attributes = nil, content = nil, options = {}) - @element = element - @attributes = attributes - @content = content - @options = options - end - - def match(subject) - @actual = subject - unless matches? - raise SpecFailedException, "Expected #{@actual} to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}" - end - end - - def inverted_match(subject) - @actual = subject - if matches? - raise SpecFailedException "Expected #{@actual} not to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}" - end - end - - private - - def matches? - actual = @actual - matched = true - - if @options[:not_closed] - matched &&= actual =~ /^#{Regexp.quote("<" + @element)}.*#{Regexp.quote(">" + (@content || ''))}$/ - else - matched &&= actual =~ /^#{Regexp.quote("<" + @element)}/ - matched &&= actual =~ /#{Regexp.quote("")}$/ - matched &&= actual =~ /#{Regexp.quote(">" + @content + ")/).size == 1) - else - matched &&= (actual.scan(%Q{ #{key}="#{value}"}).size == 1) - end - end - end - end - - !!matched - end - - def attributes_for_failure_message - if @attributes - if @attributes.empty? - "no attributes" - else - @attributes.inject([]) { |memo, n| memo << %Q{#{n[0]}="#{n[1]}"} }.join(" ") - end - else - "any attributes" - end - end - - def content_for_failure_message - if @content - if @content.empty? - "no content" - else - "#{@content.inspect} as content" - end - else - "any content" - end - end -end - -class TrueFalseExpectation - def match(subject) - unless subject == true || subject == false - raise SpecFailedException, subject.inspect + ' should be true or false' - end - end - - def inverted_match(subject) - if subject == true || subject == false - raise SpecFailedException, subject.inspect + ' should not be true or false' - end +class Expectation + def initialize(matcher) + @matcher = matcher end -end -class BeCloseExpectation - def initialize(target, tolerance = TOLERANCE) - @target = target - @tolerance = tolerance - end - - def match(subject) - if (subject - @target).abs > @tolerance - raise SpecFailedException, "#{subject.inspect} should be within #{@tolerance} of #{@target}" - end - end - - def inverted_match(subject) - if (subject - @target).abs <= @tolerance - raise SpecFailedException, "#{subject.inspect} should not be within #{@tolerance} of #{@target}" - end - end -end - -class BeComputedByExpectation - def initialize(method, args) - @method = method - @args = args - end - - def match(subject) - subject.each do |(target, *args, expected)| - actual = target.send(@method, *(@args + args)) - if actual != expected - expected_bits = - if expected.methods.include?(:bytes) - expected.bytes.map { |b| lzpad(b.to_s(2), 8) }.join(' ') - else - expected.inspect - end - - actual_bits = - actual.methods.include?(:bytes) ? actual.bytes.map { |b| lzpad(b.to_s(2), 8) }.join(' ') : actual.inspect - - raise SpecFailedException, "#{target.inspect} should compute to #{expected_bits}, but it was #{actual_bits}" - end - end - end - - def inverted_match(subject) - subject.each do |target, expected| - actual = target.send(@method, *@args) - if actual == expected - expected_bits = expected.bytes.map { |b| lzpad(b.to_s(2), 8) }.join(' ') - raise SpecFailedException, "#{target.inspect} should not compute to #{expected_bits}" - end - end - end - - private - - # TODO: Add % formatting to Natalie :-) - def lzpad(str, length) - str = '0' + str until str.length == length - str - end -end - -class BeNanExpectation def match(subject) - raise SpecFailedException, "#{subject.inspect} should be NaN" if !subject.nan? + raise SpecFailedException, @matcher.failure_message.join(' ') unless @matcher.matches?(subject) end def inverted_match(subject) - raise SpecFailedException, "#{subject.inspect} should not be NaN" if subject.nan? + raise SpecFailedException, @matcher.negative_failure_message.join(' ') if @matcher.matches?(subject) end end -class BeInfinityExpectation - def initialize(sign_of_infinity) - @sign_of_infinity = sign_of_infinity - end - - def match(subject) - raise SpecFailedException, "#{subject.inspect} should be #{"-" if negative?}Infinity" unless expected_infinity?(subject) - end - - private - - def expected_infinity?(subject) - subject.kind_of?(Float) && subject.infinite? == @sign_of_infinity - end - - def negative? - @sign_of_infinity == -1 - end -end - - class OutputExpectation def initialize(expected_out, expected_err) @expected_out = expected_out @@ -1452,79 +1138,79 @@ def should_not(*args) end def be_nil - BeNilExpectation.new + Expectation.new(BeNilMatcher.new) end def be_nan - BeNanExpectation.new + Expectation.new(BeNaNMatcher.new) end def be_negative_zero - EqlExpectation.new(-0.0) + Expectation.new(EqlMatcher.new(-0.0)) end def be_positive_zero - EqlExpectation.new(0.0) + Expectation.new(EqlMatcher.new(0.0)) end def be_positive_infinity - BeInfinityExpectation.new(1) + Expectation.new(InfinityMatcher.new(1)) end def be_negative_infinity - BeInfinityExpectation.new(-1) + Expectation.new(InfinityMatcher.new(-1)) end def be_true - EqualExpectation.new(true) + Expectation.new(BeTrueMatcher.new) end def be_false - EqualExpectation.new(false) + Expectation.new(BeFalseMatcher.new) end def be_true_or_false - TrueFalseExpectation.new() + Expectation.new(BeTrueOrFalseMatcher.new) end def be_close(target, tolerance) - BeCloseExpectation.new(target, tolerance) + Expectation.new(BeCloseMatcher.new(target, tolerance)) end def be_computed_by(method, *args) - BeComputedByExpectation.new(method, args) + Expectation.new(BeComputedByMatcher.new(method, *args)) end def be_kind_of(klass) - BeKindOfExpectation.new(klass) + Expectation.new(BeKindOfMatcher.new(klass)) end def be_an_instance_of(klass) - BeInstanceOfExpectation.new(klass) + Expectation.new(BeAnInstanceOfMatcher.new(klass)) end def be_ancestor_of(klass) - BeAncestorOfExpectation.new(klass) + Expectation.new(BeAncestorOfMatcher.new(klass)) end def block_caller - BlockCallerExpectation.new + Expectation.new(BlockingMatcher.new) end def eql(other) - EqlExpectation.new(other) + Expectation.new(EqlMatcher.new(other)) end def be_empty() - BeEmptyExpectation.new + Expectation.new(BeEmptyMatcher.new) end def equal(other) - EqualExpectation.new(other) + Expectation.new(EqualMatcher.new(other)) end def equal_element(*args) - EqualElementExpectation.new(*args) + Expectation.new(EqualElementMatcher.new(*args)) end def output(expected_stdout=nil, expected_stderr=nil) diff --git a/test/support/spec_helpers/matchers.rb b/test/support/spec_helpers/matchers.rb new file mode 100644 index 0000000000..563fb04fd1 --- /dev/null +++ b/test/support/spec_helpers/matchers.rb @@ -0,0 +1,16 @@ +require_relative 'matchers/be_an_instance_of' +require_relative 'matchers/be_ancestor_of' +require_relative 'matchers/be_close' +require_relative 'matchers/be_computed_by' +require_relative 'matchers/be_empty' +require_relative 'matchers/be_false' +require_relative 'matchers/be_kind_of' +require_relative 'matchers/be_nan' +require_relative 'matchers/be_nil' +require_relative 'matchers/be_true' +require_relative 'matchers/be_true_or_false' +require_relative 'matchers/block_caller' +require_relative 'matchers/eql' +require_relative 'matchers/equal' +require_relative 'matchers/equal_element' +require_relative 'matchers/infinity' diff --git a/test/support/spec_helpers/matchers/be_an_instance_of.rb b/test/support/spec_helpers/matchers/be_an_instance_of.rb new file mode 100644 index 0000000000..2aaea7daef --- /dev/null +++ b/test/support/spec_helpers/matchers/be_an_instance_of.rb @@ -0,0 +1,51 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeAnInstanceOfMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.instance_of?(@expected) + end + + def failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", + "to be an instance of #{@expected}"] + end + + def negative_failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", + "not to be an instance of #{@expected}"] + end +end + +module MSpecMatchers + private def be_an_instance_of(expected) + BeAnInstanceOfMatcher.new(expected) + end +end diff --git a/test/support/spec_helpers/matchers/be_ancestor_of.rb b/test/support/spec_helpers/matchers/be_ancestor_of.rb new file mode 100644 index 0000000000..7a5cb9743a --- /dev/null +++ b/test/support/spec_helpers/matchers/be_ancestor_of.rb @@ -0,0 +1,49 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeAncestorOfMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @expected.ancestors.include? @actual + end + + def failure_message + ["Expected #{@actual}", "to be an ancestor of #{@expected}"] + end + + def negative_failure_message + ["Expected #{@actual}", "not to be an ancestor of #{@expected}"] + end +end + +module MSpecMatchers + private def be_ancestor_of(expected) + BeAncestorOfMatcher.new(expected) + end +end diff --git a/test/support/spec_helpers/matchers/be_close.rb b/test/support/spec_helpers/matchers/be_close.rb new file mode 100644 index 0000000000..130b5ff735 --- /dev/null +++ b/test/support/spec_helpers/matchers/be_close.rb @@ -0,0 +1,54 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +TOLERANCE = 0.00003 unless Object.const_defined?(:TOLERANCE) +# To account for GC, context switches, other processes, load, etc. +TIME_TOLERANCE = 20.0 unless Object.const_defined?(:TIME_TOLERANCE) + +class BeCloseMatcher + def initialize(expected, tolerance) + @expected = expected + @tolerance = tolerance + end + + def matches?(actual) + @actual = actual + (@actual - @expected).abs <= @tolerance + end + + def failure_message + ["Expected #{@actual}", "to be within #{@expected} +/- #{@tolerance}"] + end + + def negative_failure_message + ["Expected #{@actual}", "not to be within #{@expected} +/- #{@tolerance}"] + end +end + +module MSpecMatchers + private def be_close(expected, tolerance) + BeCloseMatcher.new(expected, tolerance) + end +end diff --git a/test/support/spec_helpers/matchers/be_computed_by.rb b/test/support/spec_helpers/matchers/be_computed_by.rb new file mode 100644 index 0000000000..9907d03e75 --- /dev/null +++ b/test/support/spec_helpers/matchers/be_computed_by.rb @@ -0,0 +1,62 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeComputedByMatcher + def initialize(sym, *args) + @method = sym + @args = args + end + + def matches?(array) + array.each do |line| + @receiver = line.shift + @value = line.pop + @arguments = line + @arguments += @args + @actual = @receiver.send(@method, *@arguments) + return false unless @actual == @value + end + + return true + end + + def method_call + method_call = "#{@receiver.inspect}.#{@method}" + unless @arguments.empty? + method_call = "#{method_call} from #{@arguments.map { |x| x.inspect }.join(", ")}" + end + method_call + end + + def failure_message + ["Expected #{@value.inspect}", "to be computed by #{method_call} (computed #{@actual.inspect} instead)"] + end +end + +module MSpecMatchers + private def be_computed_by(sym, *args) + BeComputedByMatcher.new(sym, *args) + end +end diff --git a/test/support/spec_helpers/matchers/be_empty.rb b/test/support/spec_helpers/matchers/be_empty.rb new file mode 100644 index 0000000000..f7c4b28381 --- /dev/null +++ b/test/support/spec_helpers/matchers/be_empty.rb @@ -0,0 +1,45 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeEmptyMatcher + def matches?(actual) + @actual = actual + @actual.empty? + end + + def failure_message + ["Expected #{@actual.inspect}", "to be empty"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be empty"] + end +end + +module MSpecMatchers + private def be_empty + BeEmptyMatcher.new + end +end diff --git a/test/support/spec_helpers/matchers/be_false.rb b/test/support/spec_helpers/matchers/be_false.rb new file mode 100644 index 0000000000..a4cef73866 --- /dev/null +++ b/test/support/spec_helpers/matchers/be_false.rb @@ -0,0 +1,45 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeFalseMatcher + def matches?(actual) + @actual = actual + @actual == false + end + + def failure_message + ["Expected #{@actual.inspect}", "to be false"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be false"] + end +end + +module MSpecMatchers + private def be_false + BeFalseMatcher.new + end +end diff --git a/test/support/spec_helpers/matchers/be_kind_of.rb b/test/support/spec_helpers/matchers/be_kind_of.rb new file mode 100644 index 0000000000..a4b34faedf --- /dev/null +++ b/test/support/spec_helpers/matchers/be_kind_of.rb @@ -0,0 +1,49 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeKindOfMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.is_a?(@expected) + end + + def failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", "to be kind of #{@expected}"] + end + + def negative_failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", "not to be kind of #{@expected}"] + end +end + +module MSpecMatchers + private def be_kind_of(expected) + BeKindOfMatcher.new(expected) + end +end diff --git a/test/support/spec_helpers/matchers/be_nan.rb b/test/support/spec_helpers/matchers/be_nan.rb new file mode 100644 index 0000000000..196a861c05 --- /dev/null +++ b/test/support/spec_helpers/matchers/be_nan.rb @@ -0,0 +1,45 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeNaNMatcher + def matches?(actual) + @actual = actual + @actual.kind_of?(Float) && @actual.nan? + end + + def failure_message + ["Expected #{@actual}", "to be NaN"] + end + + def negative_failure_message + ["Expected #{@actual}", "not to be NaN"] + end +end + +module MSpecMatchers + private def be_nan + BeNaNMatcher.new + end +end diff --git a/test/support/spec_helpers/matchers/be_nil.rb b/test/support/spec_helpers/matchers/be_nil.rb new file mode 100644 index 0000000000..f622de508d --- /dev/null +++ b/test/support/spec_helpers/matchers/be_nil.rb @@ -0,0 +1,45 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeNilMatcher + def matches?(actual) + @actual = actual + @actual.nil? + end + + def failure_message + ["Expected #{@actual.inspect}", "to be nil"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be nil"] + end +end + +module MSpecMatchers + private def be_nil + BeNilMatcher.new + end +end diff --git a/test/support/spec_helpers/matchers/be_true.rb b/test/support/spec_helpers/matchers/be_true.rb new file mode 100644 index 0000000000..29ec76f905 --- /dev/null +++ b/test/support/spec_helpers/matchers/be_true.rb @@ -0,0 +1,45 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeTrueMatcher + def matches?(actual) + @actual = actual + @actual == true + end + + def failure_message + ["Expected #{@actual.inspect}", "to be true"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be true"] + end +end + +module MSpecMatchers + private def be_true + BeTrueMatcher.new + end +end diff --git a/test/support/spec_helpers/matchers/be_true_or_false.rb b/test/support/spec_helpers/matchers/be_true_or_false.rb new file mode 100644 index 0000000000..727b15dbd1 --- /dev/null +++ b/test/support/spec_helpers/matchers/be_true_or_false.rb @@ -0,0 +1,45 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BeTrueOrFalseMatcher + def matches?(actual) + @actual = actual + @actual == true || @actual == false + end + + def failure_message + ["Expected #{@actual.inspect}", "to be true or false"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be true or false"] + end +end + +module MSpecMatchers + private def be_true_or_false + BeTrueOrFalseMatcher.new + end +end diff --git a/test/support/spec_helpers/matchers/block_caller.rb b/test/support/spec_helpers/matchers/block_caller.rb new file mode 100644 index 0000000000..3263c7fee7 --- /dev/null +++ b/test/support/spec_helpers/matchers/block_caller.rb @@ -0,0 +1,62 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class BlockingMatcher + def matches?(block) + t = Thread.new do + block.call + end + + loop do + case t.status + when "sleep" # blocked + t.kill + t.join + return true + when false # terminated normally, so never blocked + t.join + return false + when nil # terminated exceptionally + t.value + else + Thread.pass + end + end + end + + def failure_message + ['Expected the given Proc', 'to block the caller'] + end + + def negative_failure_message + ['Expected the given Proc', 'to not block the caller'] + end +end + +module MSpecMatchers + private def block_caller + BlockingMatcher.new + end +end diff --git a/test/support/spec_helpers/matchers/eql.rb b/test/support/spec_helpers/matchers/eql.rb new file mode 100644 index 0000000000..51447db52f --- /dev/null +++ b/test/support/spec_helpers/matchers/eql.rb @@ -0,0 +1,51 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class EqlMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.eql?(@expected) + end + + def failure_message + ["Expected #{MSpec.format(@actual)}", + "to have same value and type as #{MSpec.format(@expected)}"] + end + + def negative_failure_message + ["Expected #{MSpec.format(@actual)}", + "not to have same value or type as #{MSpec.format(@expected)}"] + end +end + +module MSpecMatchers + private def eql(expected) + EqlMatcher.new(expected) + end +end diff --git a/test/support/spec_helpers/matchers/equal.rb b/test/support/spec_helpers/matchers/equal.rb new file mode 100644 index 0000000000..d71f119000 --- /dev/null +++ b/test/support/spec_helpers/matchers/equal.rb @@ -0,0 +1,51 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class EqualMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.equal?(@expected) + end + + def failure_message + ["Expected #{MSpec.format(@actual)}", + "to be identical to #{MSpec.format(@expected)}"] + end + + def negative_failure_message + ["Expected #{MSpec.format(@actual)}", + "not to be identical to #{MSpec.format(@expected)}"] + end +end + +module MSpecMatchers + private def equal(expected) + EqualMatcher.new(expected) + end +end diff --git a/test/support/spec_helpers/matchers/equal_element.rb b/test/support/spec_helpers/matchers/equal_element.rb new file mode 100644 index 0000000000..86a4f33eb5 --- /dev/null +++ b/test/support/spec_helpers/matchers/equal_element.rb @@ -0,0 +1,103 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class EqualElementMatcher + def initialize(element, attributes = nil, content = nil, options = {}) + @element = element + @attributes = attributes + @content = content + @options = options + end + + def matches?(actual) + @actual = actual + + matched = true + + if @options[:not_closed] + matched &&= actual =~ /^#{Regexp.quote("<" + @element)}.*#{Regexp.quote(">" + (@content || ''))}$/ + else + matched &&= actual =~ /^#{Regexp.quote("<" + @element)}/ + matched &&= actual =~ /#{Regexp.quote("")}$/ + matched &&= actual =~ /#{Regexp.quote(">" + @content + ")/).size == 1) + else + matched &&= (actual.scan(%Q{ #{key}="#{value}"}).size == 1) + end + end + end + end + + !!matched + end + + def failure_message + ["Expected #{MSpec.format(@actual)}", + "to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"] + end + + def negative_failure_message + ["Expected #{MSpec.format(@actual)}", + "not to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"] + end + + def attributes_for_failure_message + if @attributes + if @attributes.empty? + "no attributes" + else + @attributes.inject([]) { |memo, n| memo << %Q{#{n[0]}="#{n[1]}"} }.join(" ") + end + else + "any attributes" + end + end + + def content_for_failure_message + if @content + if @content.empty? + "no content" + else + "#{@content.inspect} as content" + end + else + "any content" + end + end +end + +module MSpecMatchers + private def equal_element(*args) + EqualElementMatcher.new(*args) + end +end diff --git a/test/support/spec_helpers/matchers/infinity.rb b/test/support/spec_helpers/matchers/infinity.rb new file mode 100644 index 0000000000..3dd9b8b6d5 --- /dev/null +++ b/test/support/spec_helpers/matchers/infinity.rb @@ -0,0 +1,53 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +class InfinityMatcher + def initialize(expected_sign) + @expected_sign = expected_sign + end + + def matches?(actual) + @actual = actual + @actual.kind_of?(Float) && @actual.infinite? == @expected_sign + end + + def failure_message + ["Expected #{@actual}", "to be #{"-" if @expected_sign == -1}Infinity"] + end + + def negative_failure_message + ["Expected #{@actual}", "not to be #{"-" if @expected_sign == -1}Infinity"] + end +end + +module MSpecMatchers + private def be_positive_infinity + InfinityMatcher.new(1) + end + + private def be_negative_infinity + InfinityMatcher.new(-1) + end +end diff --git a/test/support/spec_utils/format.rb b/test/support/spec_utils/format.rb new file mode 100644 index 0000000000..eb1f32b8d8 --- /dev/null +++ b/test/support/spec_utils/format.rb @@ -0,0 +1,44 @@ +=begin +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +=end + +unless Object.new.respond_to?(:pretty_inspect) + module Kernel + def pretty_inspect + inspect + end + end +end + +module MSpec + def self.format(obj) + if String === obj and obj.include?("\n") + "\n#{obj.inspect.gsub('\n', "\n")}" + else + obj.pretty_inspect.chomp + end + rescue => e + "#<#{obj.class}>(#pretty_inspect raised #{e.inspect})" + end +end