| @@ -1,17 +1,17 @@ | ||
| class SpecExpectationNotMetError < StandardError; end | ||
| class SpecExpectationNotFoundError < StandardError | ||
| def message | ||
| "No behavior expectation was found in the example" | ||
| end | ||
| end | ||
|
|
||
| class SpecExpectation | ||
| def self.fail_with(expected, actual) | ||
| if expected.to_s.size + actual.to_s.size > 80 | ||
| message = expected.to_s.chomp + "\n" + actual.to_s | ||
| else | ||
| message = expected.to_s + " " + actual.to_s | ||
| end | ||
| Kernel.raise SpecExpectationNotMetError, message | ||
| end | ||
| end |
| @@ -1,25 +1,26 @@ | ||
| class Object | ||
| NO_MATCHER_GIVEN = Object.new | ||
| def should(matcher=NO_MATCHER_GIVEN) | ||
| MSpec.expectation | ||
| MSpec.actions :expectation, MSpec.current.state | ||
| unless matcher.equal?(NO_MATCHER_GIVEN) | ||
| unless matcher.matches?(self) | ||
| SpecExpectation.fail_with(*matcher.failure_message) | ||
| end | ||
| else | ||
| SpecPositiveOperatorMatcher.new(self) | ||
| end | ||
| end | ||
|
|
||
| def should_not(matcher=NO_MATCHER_GIVEN) | ||
| MSpec.expectation | ||
| MSpec.actions :expectation, MSpec.current.state | ||
| unless matcher.equal?(NO_MATCHER_GIVEN) | ||
| if matcher.matches?(self) | ||
| SpecExpectation.fail_with(*matcher.negative_failure_message) | ||
| end | ||
| else | ||
| SpecNegativeOperatorMatcher.new(self) | ||
| end | ||
| end | ||
| end |
| @@ -0,0 +1,47 @@ | ||
| require 'mspec/guards/guard' | ||
|
|
||
| class FeatureGuard < SpecGuard | ||
| def self.enabled?(*features) | ||
| new(*features).match? | ||
| end | ||
|
|
||
| def match? | ||
| @parameters.all? { |f| MSpec.feature_enabled? f } | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| # Provides better documentation in the specs by | ||
| # naming sets of features that work together as | ||
| # a whole. Examples include :encoding, :fiber, | ||
| # :continuation, :fork. | ||
| # | ||
| # Usage example: | ||
| # | ||
| # with_feature :encoding do | ||
| # # specs for a method that provides aspects | ||
| # # of the encoding feature | ||
| # end | ||
| # | ||
| # Multiple features must all be enabled for the | ||
| # guard to run: | ||
| # | ||
| # with_feature :one, :two do | ||
| # # these specs will run if features :one AND | ||
| # # :two are enabled. | ||
| # end | ||
| # | ||
| # The implementation must explicitly enable a feature | ||
| # by adding code like the following to the .mspec | ||
| # configuration file: | ||
| # | ||
| # MSpec.enable_feature :encoding | ||
| # | ||
| def with_feature(*features) | ||
| g = FeatureGuard.new(*features) | ||
| g.name = :with_feature | ||
| yield if g.yield? | ||
| ensure | ||
| g.unregister | ||
| end | ||
| end |
| @@ -0,0 +1,66 @@ | ||
| require 'mspec/guards/guard' | ||
|
|
||
| class UnspecifiedGuard < SpecGuard | ||
| def match? | ||
| not standard? | ||
| end | ||
| end | ||
|
|
||
| class SpecifiedOnGuard < SpecGuard | ||
| def match? | ||
| if @args.include? :ruby | ||
| raise Exception, "improper use of specified_on guard" | ||
| end | ||
| not standard? and implementation?(*@args) | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| # This guard wraps one or more #specified_on guards to group them and | ||
| # document the specs. The purpose of the guard is for situations where MRI | ||
| # either does not specify Ruby behavior or where MRI's behavior is all but | ||
| # impossible to spec, for example due to relying on platform-specific | ||
| # behavior that is not easily testable from Ruby code. In such cases, it | ||
| # may be desirable for implementations to explore a specified set of | ||
| # behaviors that are explicitly documented in the specs. | ||
| # | ||
| # unspecified do | ||
| # specified_on :rubinius, :ironruby do | ||
| # it "returns true when passed :foo" do | ||
| # # ... | ||
| # end | ||
| # | ||
| # it "returns false when passed :bar" do | ||
| # # ... | ||
| # end | ||
| # end | ||
| # | ||
| # specified_on :jruby do | ||
| # it "returns true when passed :bar" do | ||
| # # ... | ||
| # end | ||
| # end | ||
| # end | ||
| # | ||
| # Note that these guards do not change the policy of the #compliant_on, | ||
| # #not_compliant_on, #deviates_on, #extended_on, and #not_supported_on | ||
| # guards. | ||
| # | ||
| def unspecified | ||
| g = UnspecifiedGuard.new | ||
| g.name = :unspecified | ||
| yield if g.yield? | ||
| ensure | ||
| g.unregister | ||
| end | ||
|
|
||
| # This guard wraps specs for one or more particular implementations. See the | ||
| # #unspecified guard for further documentation. | ||
| def specified_on(*args) | ||
| g = SpecifiedOnGuard.new(*args) | ||
| g.name = :specified_on | ||
| yield if g.yield? | ||
| ensure | ||
| g.unregister | ||
| end | ||
| end |
| @@ -0,0 +1,17 @@ | ||
| require 'mspec/guards/guard' | ||
|
|
||
| class UserGuard < SpecGuard | ||
| def match? | ||
| Process.euid != 0 | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| def as_user | ||
| g = UserGuard.new | ||
| g.name = :as_user | ||
| yield if g.yield? | ||
| ensure | ||
| g.unregister | ||
| end | ||
| end |
| @@ -1,19 +1,21 @@ | ||
| require 'mspec/helpers/argv' | ||
| require 'mspec/helpers/const_lookup' | ||
| require 'mspec/helpers/datetime' | ||
| require 'mspec/helpers/ducktype' | ||
| require 'mspec/helpers/encode' | ||
| require 'mspec/helpers/enumerator_class' | ||
| require 'mspec/helpers/environment' | ||
| require 'mspec/helpers/fixture' | ||
| require 'mspec/helpers/flunk' | ||
| require 'mspec/helpers/fmode' | ||
| require 'mspec/helpers/fs' | ||
| require 'mspec/helpers/hash' | ||
| require 'mspec/helpers/io' | ||
| require 'mspec/helpers/language_version' | ||
| require 'mspec/helpers/mock_to_path' | ||
| require 'mspec/helpers/numeric' | ||
| require 'mspec/helpers/ruby_exe' | ||
| require 'mspec/helpers/scratch' | ||
| require 'mspec/helpers/stasy' | ||
| require 'mspec/helpers/singleton_class' | ||
| require 'mspec/helpers/tmp' |
| @@ -0,0 +1,30 @@ | ||
| require 'date' | ||
|
|
||
| class Object | ||
| # The new_datetime helper makes writing DateTime specs more simple by | ||
| # providing default constructor values and accepting a Hash of only the | ||
| # constructor values needed for the particular spec. For example: | ||
| # | ||
| # new_datetime :hour => 1, :minute => 20 | ||
| # | ||
| # Possible keys are: | ||
| # :year, :month, :day, :hour, :minute, :second, :offset and :sg. | ||
|
|
||
| MSPEC_DATETIME_OPTIONS = { | ||
| :year => -4712, | ||
| :month => 1, | ||
| :day => 1, | ||
| :hour => 0, | ||
| :minute => 0, | ||
| :second => 0, | ||
| :offset => 0, | ||
| :sg => Date::ITALY | ||
| } | ||
|
|
||
| def new_datetime(opts={}) | ||
| value = MSPEC_DATETIME_OPTIONS.dup.merge opts | ||
|
|
||
| DateTime.new value[:year], value[:month], value[:day], value[:hour], | ||
| value[:minute], value[:second], value[:offset], value[:sg] | ||
| end | ||
| end |
| @@ -0,0 +1,21 @@ | ||
| require 'mspec/guards/feature' | ||
|
|
||
| class Object | ||
| # Helper to handle String encodings. The +str+ and +encoding+ parameters | ||
| # must be Strings and an ArgumentError will be raised if not. This ensures | ||
| # that the encode() helper can be used regardless of whether Encoding exits. | ||
| # The helper is a no-op (i.e. passes through +str+ unmodified) if the | ||
| # :encoding feature is not enabled (see with_feature guard). If the | ||
| # :encoding feature is enabled, +str+.force_encoding(+encoding+) is called. | ||
| def encode(str, encoding) | ||
| unless str.is_a? String and encoding.is_a? String | ||
| raise ArgumentError, "encoding name must be a String" | ||
| end | ||
|
|
||
| if FeatureGuard.enabled? :encoding | ||
| str.force_encoding encoding | ||
| end | ||
|
|
||
| str | ||
| end | ||
| end |
| @@ -1,5 +1,5 @@ | ||
| class Object | ||
| def flunk(msg="This example is a failure") | ||
| SpecExpectation.fail_with "Failed:", msg | ||
| end | ||
| end |
| @@ -0,0 +1,15 @@ | ||
| require 'mspec/guards/feature' | ||
|
|
||
| class Object | ||
| # This helper simplifies passing file access modes regardless of | ||
| # whether the :encoding feature is enabled. Only the access specifier | ||
| # itself will be returned if :encoding is not enabled. Otherwise, | ||
| # the full mode string will be returned (i.e. the helper is a no-op). | ||
| def fmode(mode) | ||
| if FeatureGuard.enabled? :encoding | ||
| mode | ||
| else | ||
| mode.split(':').first | ||
| end | ||
| end | ||
| end |
| @@ -1,17 +1,36 @@ | ||
| class IOStub < String | ||
| def write(*str) | ||
| self << str.join | ||
| end | ||
|
|
||
| def print(*str) | ||
| write(str.join + $\.to_s) | ||
| end | ||
|
|
||
| def puts(*str) | ||
| write(str.collect { |s| s.to_s.chomp }.concat([nil]).join("\n")) | ||
| end | ||
|
|
||
| def printf(format, *args) | ||
| self << sprintf(format, *args) | ||
| end | ||
|
|
||
| def flush | ||
| self | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| # Creates a "bare" file descriptor (i.e. one that is not associated | ||
| # with any Ruby object). The file descriptor can safely be passed | ||
| # to IO.new without creating a Ruby object alias to the fd. | ||
| def new_fd(name, mode="w:utf-8") | ||
| IO.sysopen name, fmode(mode) | ||
| end | ||
|
|
||
| # Creates an IO instance for a temporary file name. The file | ||
| # must be deleted. | ||
| def new_io(name, mode="w:utf-8") | ||
| IO.new new_fd(name, fmode(mode)), fmode(mode) | ||
| end | ||
| end |
| @@ -0,0 +1,89 @@ | ||
| class Object | ||
| def nan_value | ||
| 0/0.0 | ||
| end | ||
|
|
||
| def infinity_value | ||
| 1/0.0 | ||
| end | ||
|
|
||
| def bignum_value(plus=0) | ||
| 0x8000_0000_0000_0000 + plus | ||
| end | ||
|
|
||
| # This is a bit hairy, but we need to be able to write specs that cover the | ||
| # boundary between Fixnum and Bignum for operations like Fixnum#<<. Since | ||
| # this boundary is implementation-dependent, we use these helpers to write | ||
| # specs based on the relationship between values rather than specific | ||
| # values. | ||
| guard = SpecGuard.new | ||
|
|
||
| if guard.standard? | ||
| if guard.wordsize? 32 | ||
| def fixnum_max() | ||
| (2**30) - 1 | ||
| end | ||
|
|
||
| def fixnum_min() | ||
| -(2**30) | ||
| end | ||
| elsif guard.wordsize? 64 | ||
| def fixnum_max() | ||
| (2**62) - 1 | ||
| end | ||
|
|
||
| def fixnum_min() | ||
| -(2**62) | ||
| end | ||
| end | ||
| elsif guard.implementation? :rubinius | ||
| def fixnum_max() | ||
| Fixnum::MAX | ||
| end | ||
|
|
||
| def fixnum_min() | ||
| Fixnum::MIN | ||
| end | ||
| elsif guard.implementation? :jruby | ||
| # Values from jruby/test/testFixnumBignumAutoconversion.rb | ||
| def fixnum_max() | ||
| 9223372036854775807 | ||
| end | ||
|
|
||
| def fixnum_min() | ||
| -9223372036854775808 | ||
| end | ||
| elsif guard.implementation? :ironruby | ||
| def fixnum_max() | ||
| raise "fixnum_max() helper not implemented" | ||
| end | ||
|
|
||
| def fixnum_min() | ||
| raise "fixnum_min() helper not implemented" | ||
| end | ||
| elsif guard.implementation? :maglev | ||
| def fixnum_max() | ||
| raise "fixnum_max() helper not implemented" | ||
| end | ||
|
|
||
| def fixnum_min() | ||
| raise "fixnum_min() helper not implemented" | ||
| end | ||
| elsif guard.implementation? :macruby | ||
| def fixnum_max() | ||
| raise "fixnum_max() helper not implemented" | ||
| end | ||
|
|
||
| def fixnum_min() | ||
| raise "fixnum_min() helper not implemented" | ||
| end | ||
| else | ||
| def fixnum_max() | ||
| raise "unknown implementation for fixnum_max() helper" | ||
| end | ||
|
|
||
| def fixnum_min() | ||
| raise "unknown implementation for fixnum_min() helper" | ||
| end | ||
| end | ||
| end |
| @@ -1,123 +1,138 @@ | ||
| require 'mspec/utils/ruby_name' | ||
| require 'mspec/guards/platform' | ||
|
|
||
| # The ruby_exe helper provides a wrapper for invoking the | ||
| # same Ruby interpreter as the one running the specs and | ||
| # getting the output from running the code. If +code+ is a | ||
| # file that exists, it will be run. Otherwise, +code+ should | ||
| # be Ruby code that will be run with the -e command line | ||
| # option. For example: | ||
| # | ||
| # ruby_exe('path/to/some/file.rb') | ||
| # | ||
| # will be executed as | ||
| # | ||
| # `#{RUBY_EXE} #{'path/to/some/file.rb'}` | ||
| # | ||
| # while | ||
| # | ||
| # ruby_exe('puts "hello, world."') | ||
| # | ||
| # will be executed as | ||
| # | ||
| # `#{RUBY_EXE} -e #{'puts "hello, world."'}` | ||
| # | ||
| # The ruby_exe helper also accepts an options hash with two | ||
| # keys: :options and :args. For example: | ||
| # | ||
| # ruby_exe('file.rb', :options => "-w", :args => "> file.txt") | ||
| # | ||
| # will be executed as | ||
| # | ||
| # `#{RUBY_EXE} -w #{'file.rb'} > file.txt` | ||
| # | ||
| # If +nil+ is passed for the first argument, the command line | ||
| # will be built only from the options hash. | ||
| # | ||
| # The RUBY_EXE constant can be set explicitly since the value | ||
| # is used each time ruby_exe is invoked. The mspec runner script | ||
| # will set ENV['RUBY_EXE'] to the name of the executable used | ||
| # to invoke the mspec-run script. The value of RUBY_EXE will be | ||
| # constructed as follows: | ||
| # | ||
| # 1. the value of ENV['RUBY_EXE'] | ||
| # 2. an explicit value based on RUBY_NAME | ||
| # 3. cwd/(RUBY_NAME + $(EXEEXT) || $(exeext) || '') | ||
| # 4. $(bindir)/$(RUBY_INSTALL_NAME) | ||
| # | ||
| # The value will only be used if the file exists and is executable. | ||
| # | ||
| # These 4 ways correspond to the following scenarios: | ||
| # | ||
| # 1. Using the MSpec runner scripts, the name of the | ||
| # executable is explicitly passed by ENV['RUBY_EXE'] | ||
| # so there is no ambiguity. | ||
| # | ||
| # Otherwise, if using RSpec (or something else) | ||
| # | ||
| # 2. Running the specs while developing an alternative | ||
| # Ruby implementation. This explicitly names the | ||
| # executable in the development directory based on | ||
| # the value of RUBY_NAME, which is probably initialized | ||
| # from the value of RUBY_ENGINE. | ||
| # 3. Running the specs within the source directory for | ||
| # some implementation. (E.g. a local build directory.) | ||
| # 4. Running the specs against some installed Ruby | ||
| # implementation. | ||
|
|
||
| class Object | ||
| def ruby_exe_options(option) | ||
| case option | ||
| when :env | ||
| ENV['RUBY_EXE'] | ||
| when :engine | ||
| case RUBY_NAME | ||
| when 'rbx' | ||
| if SpecGuard.ruby_version < "1.9" | ||
| "bin/rbx" | ||
| else | ||
| "bin/rbx -X19" | ||
| end | ||
| when 'jruby' | ||
| "bin/jruby" | ||
| when 'maglev' | ||
| "maglev-ruby" | ||
| when 'ironruby' | ||
| "ir" | ||
| end | ||
| when :name | ||
| bin = RUBY_NAME + (RbConfig::CONFIG['EXEEXT'] || RbConfig::CONFIG['exeext'] || '') | ||
| File.join(".", bin) | ||
| when :install_name | ||
| bin = RbConfig::CONFIG["RUBY_INSTALL_NAME"] || RbConfig::CONFIG["ruby_install_name"] | ||
| bin << (RbConfig::CONFIG['EXEEXT'] || RbConfig::CONFIG['exeext'] || '') | ||
| File.join(RbConfig::CONFIG['bindir'], bin) | ||
| end | ||
| end | ||
|
|
||
| def resolve_ruby_exe | ||
| [:env, :engine, :name, :install_name].each do |option| | ||
| next unless cmd = ruby_exe_options(option) | ||
| exe = cmd.split.first | ||
|
|
||
| # It has been reported that File.executable is not reliable | ||
| # on Windows platforms (see commit 56bc555c). So, we check the | ||
| # platform. | ||
| if File.exists?(exe) and (PlatformGuard.windows? or File.executable?(exe)) | ||
| return cmd | ||
| end | ||
| end | ||
| nil | ||
| end | ||
|
|
||
| def ruby_exe(code, opts = {}) | ||
| body = code | ||
| working_dir = opts[:dir] || "." | ||
| Dir.chdir(working_dir) do | ||
| if code and not File.exists?(code) | ||
| if opts[:escape] | ||
| code = "'#{code}'" | ||
| else | ||
| code = code.inspect | ||
| end | ||
| body = "-e #{code}" | ||
| end | ||
| cmd = [RUBY_EXE, ENV['RUBY_FLAGS'], opts[:options], body, opts[:args]] | ||
| `#{cmd.compact.join(' ')}` | ||
| end | ||
| end | ||
|
|
||
| #RHO | ||
| # unless Object.const_defined?(:RUBY_EXE) and RUBY_EXE | ||
| # require 'rbconfig' | ||
| # | ||
| # RUBY_EXE = resolve_ruby_exe or | ||
| # raise Exception, "Unable to find a suitable ruby executable." | ||
| # end | ||
| #RHO | ||
| end |
| @@ -0,0 +1,7 @@ | ||
| class Object | ||
| unless method_defined? :singleton_class | ||
| def singleton_class | ||
| class << self; self; end | ||
| end | ||
| end | ||
| end |
| @@ -0,0 +1,33 @@ | ||
| require 'mspec/utils/version' | ||
| require 'mspec/guards/guard' | ||
|
|
||
| class Object | ||
| # Accepts either a single argument or an Array of arguments. If RUBY_VERSION | ||
| # is less than 1.9, converts the argument(s) to Strings; otherwise, converts | ||
| # the argument(s) to Symbols. | ||
| # | ||
| # If one argument is passed, the converted argument is returned. If an Array | ||
| # is passed, an Array is returned. | ||
| # | ||
| # For example, if RUBY_VERSION == 1.8.7 | ||
| # | ||
| # stasy(:some) => "some" | ||
| # stasy("nom") => "nom" | ||
| # | ||
| # while if RUBY_VERSION == 1.9.0 | ||
| # | ||
| # stasy(:some) => :some | ||
| # stasy("nom") => :nom | ||
|
|
||
| def stasy(one, *rest) | ||
| era = SpecVersion.new(SpecGuard.ruby_version) < "1.9" | ||
| convert = era ? :to_s : :to_sym | ||
|
|
||
| one = one.send convert | ||
| if rest.empty? | ||
| one | ||
| else | ||
| [one].concat rest.map { |x| x.send convert } | ||
| end | ||
| end | ||
| end |
| @@ -1,32 +1,40 @@ | ||
| # Creates a temporary directory in the current working directory | ||
| # for temporary files created while running the specs. All specs | ||
| # should clean up any temporary files created so that the temp | ||
| # directory is empty when the process exits. | ||
|
|
||
| SPEC_TEMP_DIR = "#{File.expand_path(Dir.pwd)}/rubyspec_temp" | ||
|
|
||
| SPEC_TEMP_UNIQUIFIER = "0" | ||
|
|
||
| at_exit do | ||
| begin | ||
| Dir.delete SPEC_TEMP_DIR if File.directory? SPEC_TEMP_DIR | ||
| rescue SystemCallError | ||
| STDERR.puts <<-EOM | ||
| ----------------------------------------------------- | ||
| The rubyspec temp directory is not empty. Ensure that | ||
| all specs are cleaning up temporary files. | ||
| ----------------------------------------------------- | ||
| EOM | ||
| rescue Object => e | ||
| STDERR.puts "failed to remove spec temp directory" | ||
| STDERR.puts e.message | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| def tmp(name, uniquify=true) | ||
| Dir.mkdir SPEC_TEMP_DIR unless File.exists? SPEC_TEMP_DIR | ||
|
|
||
| if uniquify and !name.empty? | ||
| slash = name.rindex "/" | ||
| index = slash ? slash + 1 : 0 | ||
| name.insert index, "#{SPEC_TEMP_UNIQUIFIER.succ!}-" | ||
| end | ||
|
|
||
| File.join SPEC_TEMP_DIR, name | ||
| end | ||
| end |
| @@ -0,0 +1,36 @@ | ||
| 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 | ||
| return false unless @receiver.send(@method, *@arguments) == @value | ||
| end | ||
|
|
||
| return true | ||
| end | ||
|
|
||
| def method_call | ||
| method_call = "#{@receiver.inspect}.#{@method}" | ||
| unless @arguments.empty? | ||
| 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}"] | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| def be_computed_by(sym, *args) | ||
| BeComputedByMatcher.new(sym, *args) | ||
| end | ||
| end |
| @@ -0,0 +1,35 @@ | ||
| class BeComputedByFunctionMatcher | ||
| def initialize(sym, *args) | ||
| @function = sym | ||
| @args = args | ||
| end | ||
|
|
||
| def matches?(array) | ||
| array.each do |line| | ||
| @value = line.pop | ||
| @arguments = line | ||
| @arguments += @args | ||
| return false unless send(@function, *@arguments) == @value | ||
| end | ||
|
|
||
| return true | ||
| end | ||
|
|
||
| def function_call | ||
| function_call = "#{@function}" | ||
| unless @arguments.empty? | ||
| function_call << "(#{@arguments.map { |x| x.inspect }.join(", ")})" | ||
| end | ||
| function_call | ||
| end | ||
|
|
||
| def failure_message | ||
| ["Expected #{@value.inspect}", "to be computed by #{function_call}"] | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| def be_computed_by_function(sym, *args) | ||
| BeComputedByFunctionMatcher.new(sym, *args) | ||
| end | ||
| end |
| @@ -0,0 +1,24 @@ | ||
| class BeValidDNSName | ||
| # http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address | ||
| # ftp://ftp.rfc-editor.org/in-notes/rfc3696.txt | ||
| VALID_DNS = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])\.?$/ | ||
|
|
||
| def matches?(actual) | ||
| @actual = actual | ||
| (VALID_DNS =~ @actual) == 0 | ||
| end | ||
|
|
||
| def failure_message | ||
| ["Expected '#{@actual}'", "to be a valid DNS name"] | ||
| end | ||
|
|
||
| def negative_failure_message | ||
| ["Expected '#{@actual}'", "not to be a valid DNS name"] | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| def be_valid_DNS_name | ||
| BeValidDNSName.new | ||
| end | ||
| end |
| @@ -0,0 +1,12 @@ | ||
| require 'mspec/matchers/variable' | ||
|
|
||
| class HaveClassVariableMatcher < VariableMatcher | ||
| self.variables_method = :class_variables | ||
| self.description = 'class variable' | ||
| end | ||
|
|
||
| class Object | ||
| def have_class_variable(variable) | ||
| HaveClassVariableMatcher.new(variable) | ||
| end | ||
| end |
| @@ -1,30 +1,12 @@ | ||
| require 'mspec/matchers/variable' | ||
|
|
||
| class HaveConstantMatcher < VariableMatcher | ||
| self.variables_method = :constants | ||
| self.description = 'constant' | ||
| end | ||
|
|
||
| class Object | ||
| def have_constant(variable) | ||
| HaveConstantMatcher.new(variable) | ||
| end | ||
| end |
| @@ -0,0 +1,49 @@ | ||
| require 'mspec/guards/feature' | ||
| require 'mspec/helpers/fmode' | ||
|
|
||
| class HaveDataMatcher | ||
| def initialize(data, mode="rb:binary") | ||
| @data = data | ||
| @mode = mode | ||
| end | ||
|
|
||
| def matches?(name) | ||
| @name = name | ||
|
|
||
| if FeatureGuard.enabled? :encoding | ||
| size = @data.bytesize | ||
| else | ||
| size = @data.size | ||
| end | ||
|
|
||
| File.open @name, fmode(@mode) do |f| | ||
| return f.read(size) == @data | ||
| end | ||
| end | ||
|
|
||
| def failure_message | ||
| ["Expected #{@name}", | ||
| "to have data #{@data.pretty_inspect}"] | ||
| end | ||
|
|
||
| def negative_failure_message | ||
| ["Expected #{@name}", | ||
| "not to have data #{@data.pretty_inspect}"] | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| # Opens a file specified by the string the matcher is called on | ||
| # and compares the +data+ passed to the matcher with the contents | ||
| # of the file. Expects to match the first N bytes of the file | ||
| # with +data+. For example, suppose @name is the name of a file: | ||
| # | ||
| # @name.should have_data("123") | ||
| # | ||
| # passes if the file @name has "123" as the first 3 bytes. The | ||
| # file can contain more bytes than +data+. The extra bytes do not | ||
| # affect the result. | ||
| def have_data(data, mode="rb:binary") | ||
| HaveDataMatcher.new(data, mode) | ||
| end | ||
| end |
| @@ -3,7 +3,7 @@ | ||
| class HaveInstanceMethodMatcher < MethodMatcher | ||
| def matches?(mod) | ||
| @mod = mod | ||
| mod.instance_methods(@include_super).include? @method | ||
| end | ||
|
|
||
| def failure_message | ||
| @@ -0,0 +1,12 @@ | ||
| require 'mspec/matchers/variable' | ||
|
|
||
| class HaveInstanceVariableMatcher < VariableMatcher | ||
| self.variables_method = :instance_variables | ||
| self.description = 'instance variable' | ||
| end | ||
|
|
||
| class Object | ||
| def have_instance_variable(variable) | ||
| HaveInstanceVariableMatcher.new(variable) | ||
| end | ||
| end |
| @@ -3,7 +3,7 @@ | ||
| class HaveMethodMatcher < MethodMatcher | ||
| def matches?(mod) | ||
| @mod = mod | ||
| @mod.methods(@include_super).include? @method | ||
| end | ||
|
|
||
| def failure_message | ||
| @@ -3,7 +3,7 @@ | ||
| class HavePrivateInstanceMethodMatcher < MethodMatcher | ||
| def matches?(mod) | ||
| @mod = mod | ||
| mod.private_instance_methods(@include_super).include? @method | ||
| end | ||
|
|
||
| def failure_message | ||
| @@ -0,0 +1,24 @@ | ||
| require 'mspec/matchers/method' | ||
|
|
||
| class HavePrivateMethodMatcher < MethodMatcher | ||
| def matches?(mod) | ||
| @mod = mod | ||
| mod.private_methods(@include_super).include? @method | ||
| end | ||
|
|
||
| def failure_message | ||
| ["Expected #{@mod} to have private method '#{@method.to_s}'", | ||
| "but it does not"] | ||
| end | ||
|
|
||
| def negative_failure_message | ||
| ["Expected #{@mod} NOT to have private method '#{@method.to_s}'", | ||
| "but it does"] | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| def have_private_method(method, include_super=true) | ||
| HavePrivateMethodMatcher.new method, include_super | ||
| end | ||
| end |
| @@ -0,0 +1,24 @@ | ||
| require 'mspec/matchers/method' | ||
|
|
||
| class HaveProtectedInstanceMethodMatcher < MethodMatcher | ||
| def matches?(mod) | ||
| @mod = mod | ||
| mod.protected_instance_methods(@include_super).include? @method | ||
| end | ||
|
|
||
| def failure_message | ||
| ["Expected #{@mod} to have protected instance method '#{@method.to_s}'", | ||
| "but it does not"] | ||
| end | ||
|
|
||
| def negative_failure_message | ||
| ["Expected #{@mod} NOT to have protected instance method '#{@method.to_s}'", | ||
| "but it does"] | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| def have_protected_instance_method(method, include_super=true) | ||
| HaveProtectedInstanceMethodMatcher.new method, include_super | ||
| end | ||
| end |
| @@ -3,7 +3,7 @@ | ||
| class HavePublicInstanceMethodMatcher < MethodMatcher | ||
| def matches?(mod) | ||
| @mod = mod | ||
| mod.public_instance_methods(@include_super).include? @method | ||
| end | ||
|
|
||
| def failure_message | ||
| @@ -0,0 +1,24 @@ | ||
| require 'mspec/matchers/method' | ||
|
|
||
| class HaveSingletonMethodMatcher < MethodMatcher | ||
| def matches?(obj) | ||
| @obj = obj | ||
| obj.singleton_methods(@include_super).include? @method | ||
| end | ||
|
|
||
| def failure_message | ||
| ["Expected #{@obj} to have singleton method '#{@method.to_s}'", | ||
| "but it does not"] | ||
| end | ||
|
|
||
| def negative_failure_message | ||
| ["Expected #{@obj} NOT to have singleton method '#{@method.to_s}'", | ||
| "but it does"] | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| def have_singleton_method(method, include_super=true) | ||
| HaveSingletonMethodMatcher.new method, include_super | ||
| end | ||
| end |
| @@ -1,71 +1,71 @@ | ||
| require 'mspec/helpers/tmp' | ||
| require 'fileutils' | ||
|
|
||
| # Lower-level output speccing mechanism for a single | ||
| # output stream. Unlike OutputMatcher which provides | ||
| # methods to capture the output, we actually replace | ||
| # the FD itself so that there is no reliance on a | ||
| # certain method being used. | ||
| class OutputToFDMatcher | ||
| def initialize(expected, to) | ||
| @to, @expected = to, expected | ||
|
|
||
| case @to | ||
| when STDOUT | ||
| @to_name = "STDOUT" | ||
| when STDERR | ||
| @to_name = "STDERR" | ||
| when IO | ||
| @to_name = @to.object_id.to_s | ||
| else | ||
| raise ArgumentError, "#{@to.inspect} is not a supported output target" | ||
| end | ||
| end | ||
|
|
||
| def matches?(block) | ||
| old_to = @to.dup | ||
| out = File.open(tmp("mspec_output_to_#{$$}_#{Time.now.to_i}"), 'w+') | ||
|
|
||
| # Replacing with a file handle so that Readline etc. work | ||
| @to.reopen out | ||
|
|
||
| block.call | ||
|
|
||
| ensure | ||
| begin | ||
| @to.reopen old_to | ||
|
|
||
| out.rewind | ||
| @actual = out.read | ||
|
|
||
| case @expected | ||
| when Regexp | ||
| return !(@actual =~ @expected).nil? | ||
| else | ||
| return @actual == @expected | ||
| end | ||
|
|
||
| # Clean up | ||
| ensure | ||
| out.close unless out.closed? | ||
| FileUtils.rm out.path | ||
| end | ||
|
|
||
| return true | ||
| end | ||
|
|
||
| def failure_message() | ||
| ["Expected (#{@to_name}): #{@expected.inspect}\n", | ||
| "#{'but got'.rjust(@to_name.length + 10)}: #{@actual.inspect}\nBacktrace"] | ||
| end | ||
|
|
||
| def negative_failure_message() | ||
| ["Expected output (#{@to_name}) to NOT be:\n", @actual.inspect] | ||
| end | ||
| end | ||
|
|
||
| class Object | ||
| def output_to_fd(what, where = STDOUT) | ||
| OutputToFDMatcher.new what, where | ||
| end | ||
| end |
| @@ -3,6 +3,6 @@ | ||
| module StringSymbolAdapter | ||
| def convert_name(name) | ||
| version = SpecVersion.new(RUBY_VERSION) <=> "1.9" | ||
| version < 0 ? name.to_s : name.to_sym | ||
| end | ||
| end | ||
| @@ -0,0 +1,28 @@ | ||
| require 'mspec/matchers/stringsymboladapter' | ||
|
|
||
| class VariableMatcher | ||
| include StringSymbolAdapter | ||
|
|
||
| class << self | ||
| attr_accessor :variables_method, :description | ||
| end | ||
|
|
||
| def initialize(variable) | ||
| @variable = convert_name(variable) | ||
| end | ||
|
|
||
| def matches?(object) | ||
| @object = object | ||
| @object.send(self.class.variables_method).include? @variable | ||
| end | ||
|
|
||
| def failure_message | ||
| ["Expected #{@object} to have #{self.class.description} '#{@variable}'", | ||
| "but it does not"] | ||
| end | ||
|
|
||
| def negative_failure_message | ||
| ["Expected #{@object} NOT to have #{self.class.description} '#{@variable}'", | ||
| "but it does"] | ||
| end | ||
| end |
| @@ -2,6 +2,8 @@ | ||
|
|
||
| class DebugAction < ActionFilter | ||
| def before(state) | ||
| require 'rubygems' | ||
| require 'ruby-debug' | ||
| Kernel.debugger if self === state.description | ||
| end | ||
|
|
||
| @@ -8,3 +8,4 @@ | ||
| require 'mspec/runner/formatters/spinner' | ||
| require 'mspec/runner/formatters/method' | ||
| require 'mspec/runner/formatters/yaml' | ||
| require 'mspec/runner/formatters/profile' | ||
| @@ -0,0 +1,70 @@ | ||
| require 'mspec/expectations/expectations' | ||
| require 'mspec/runner/formatters/dotted' | ||
|
|
||
| class ProfileFormatter < DottedFormatter | ||
| def initialize(out=nil) | ||
| super | ||
|
|
||
| @describe_name = nil | ||
| @describe_time = nil | ||
| @describes = [] | ||
| @its = [] | ||
| end | ||
|
|
||
| def register | ||
| super | ||
| MSpec.register :enter, self | ||
| end | ||
|
|
||
| # Callback for the MSpec :enter event. Prints the | ||
| # +describe+ block string. | ||
| def enter(describe) | ||
| if @describe_time | ||
| @describes << [@describe_name, Time.now.to_f - @describe_time] | ||
| end | ||
|
|
||
| @describe_name = describe | ||
| @describe_time = Time.now.to_f | ||
| end | ||
|
|
||
| # Callback for the MSpec :before event. Prints the | ||
| # +it+ block string. | ||
| def before(state) | ||
| super | ||
|
|
||
| @it_name = state.it | ||
| @it_time = Time.now.to_f | ||
| end | ||
|
|
||
| # Callback for the MSpec :after event. Prints a | ||
| # newline to finish the description string output. | ||
| def after(state) | ||
| @its << [@describe_name, @it_name, Time.now.to_f - @it_time] | ||
| super | ||
| end | ||
|
|
||
| def finish | ||
| puts "\nProfiling info:" | ||
|
|
||
| desc = @describes.sort { |a,b| b.last <=> a.last } | ||
| desc.delete_if { |a| a.last <= 0.001 } | ||
| show = desc[0, 100] | ||
|
|
||
| puts "Top #{show.size} describes:" | ||
|
|
||
| show.each do |des, time| | ||
| printf "%3.3f - %s\n", time, des | ||
| end | ||
|
|
||
| its = @its.sort { |a,b| b.last <=> a.last } | ||
| its.delete_if { |a| a.last <= 0.001 } | ||
| show = its[0, 100] | ||
|
|
||
| puts "\nTop #{show.size} its:" | ||
| show.each do |des, it, time| | ||
| printf "%3.3f - %s %s\n", time, des, it | ||
| end | ||
|
|
||
| super | ||
| end | ||
| end |
| @@ -4,7 +4,7 @@ class Object | ||
| def it_behaves_like(desc, meth, obj=nil) | ||
| send :before, :all do | ||
| @method = meth | ||
| @object = obj | ||
| end | ||
|
|
||
| send :it_should_behave_like, desc.to_s | ||
| @@ -3,6 +3,6 @@ | ||
| RUBY_NAME = RUBY_ENGINE | ||
| else | ||
| require 'rbconfig' | ||
| RUBY_NAME = ::RbConfig::CONFIG["RUBY_INSTALL_NAME"] || RbConfig::CONFIG["ruby_install_name"] | ||
| end | ||
| end | ||
| @@ -1,5 +1,5 @@ | ||
| require 'mspec/utils/version' | ||
|
|
||
| module MSpec | ||
| VERSION = SpecVersion.new "1.5.17" | ||
| end |