summaryrefslogtreecommitdiff
path: root/spec/mspec/lib/mspec/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'spec/mspec/lib/mspec/helpers')
-rw-r--r--spec/mspec/lib/mspec/helpers/argf.rb37
-rw-r--r--spec/mspec/lib/mspec/helpers/argv.rb46
-rw-r--r--spec/mspec/lib/mspec/helpers/datetime.rb51
-rw-r--r--spec/mspec/lib/mspec/helpers/fixture.rb26
-rw-r--r--spec/mspec/lib/mspec/helpers/flunk.rb5
-rw-r--r--spec/mspec/lib/mspec/helpers/fs.rb62
-rw-r--r--spec/mspec/lib/mspec/helpers/io.rb113
-rw-r--r--spec/mspec/lib/mspec/helpers/mock_to_path.rb8
-rw-r--r--spec/mspec/lib/mspec/helpers/numeric.rb72
-rw-r--r--spec/mspec/lib/mspec/helpers/ruby_exe.rb178
-rw-r--r--spec/mspec/lib/mspec/helpers/scratch.rb17
-rw-r--r--spec/mspec/lib/mspec/helpers/tmp.rb45
12 files changed, 660 insertions, 0 deletions
diff --git a/spec/mspec/lib/mspec/helpers/argf.rb b/spec/mspec/lib/mspec/helpers/argf.rb
new file mode 100644
index 0000000000..1ba48b9378
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/argf.rb
@@ -0,0 +1,37 @@
+class Object
+ # Convenience helper for specs using ARGF.
+ # Set @argf to an instance of ARGF.class with the given +argv+.
+ # That instance must be used instead of ARGF as ARGF is global
+ # and it is not always possible to reset its state correctly.
+ #
+ # The helper yields to the block and then close
+ # the files open by the instance. Example:
+ #
+ # describe "That" do
+ # it "does something" do
+ # argf ['a', 'b'] do
+ # # do something
+ # end
+ # end
+ # end
+ def argf(argv)
+ if argv.empty? or argv.length > 2
+ raise "Only 1 or 2 filenames are allowed for the argf helper so files can be properly closed: #{argv.inspect}"
+ end
+ @argf ||= nil
+ raise "Cannot nest calls to the argf helper" if @argf
+
+ @argf = ARGF.class.new(*argv)
+ @__mspec_saved_argf_file__ = @argf.file
+ begin
+ yield
+ ensure
+ file1 = @__mspec_saved_argf_file__
+ file2 = @argf.file # Either the first file or the second
+ file1.close if !file1.closed? and file1 != STDIN
+ file2.close if !file2.closed? and file2 != STDIN
+ @argf = nil
+ @__mspec_saved_argf_file__ = nil
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/argv.rb b/spec/mspec/lib/mspec/helpers/argv.rb
new file mode 100644
index 0000000000..c8cbbf2ac3
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/argv.rb
@@ -0,0 +1,46 @@
+class Object
+ # Convenience helper for altering ARGV. Saves the
+ # value of ARGV and sets it to +args+. If a block
+ # is given, yields to the block and then restores
+ # the value of ARGV. The previously saved value of
+ # ARGV can be restored by passing +:restore+. The
+ # former is useful in a single spec. The latter is
+ # useful in before/after actions. For example:
+ #
+ # describe "This" do
+ # before do
+ # argv ['a', 'b']
+ # end
+ #
+ # after do
+ # argv :restore
+ # end
+ #
+ # it "does something" do
+ # # do something
+ # end
+ # end
+ #
+ # describe "That" do
+ # it "does something" do
+ # argv ['a', 'b'] do
+ # # do something
+ # end
+ # end
+ # end
+ def argv(args)
+ if args == :restore
+ ARGV.replace(@__mspec_saved_argv__ || [])
+ else
+ @__mspec_saved_argv__ = ARGV.dup
+ ARGV.replace args
+ if block_given?
+ begin
+ yield
+ ensure
+ argv :restore
+ end
+ end
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/datetime.rb b/spec/mspec/lib/mspec/helpers/datetime.rb
new file mode 100644
index 0000000000..4cb57bdaa1
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/datetime.rb
@@ -0,0 +1,51 @@
+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.
+
+ def new_datetime(opts={})
+ require 'date'
+
+ value = {
+ :year => -4712,
+ :month => 1,
+ :day => 1,
+ :hour => 0,
+ :minute => 0,
+ :second => 0,
+ :offset => 0,
+ :sg => Date::ITALY
+ }.merge opts
+
+ DateTime.new value[:year], value[:month], value[:day], value[:hour],
+ value[:minute], value[:second], value[:offset], value[:sg]
+ end
+
+ def with_timezone(name, offset = nil, daylight_saving_zone = "")
+ zone = name.dup
+
+ if offset
+ # TZ convention is backwards
+ offset = -offset
+
+ zone += offset.to_s
+ zone += ":00:00"
+ end
+ zone += daylight_saving_zone
+
+ old = ENV["TZ"]
+ ENV["TZ"] = zone
+
+ begin
+ yield
+ ensure
+ ENV["TZ"] = old
+ end
+ end
+
+end
diff --git a/spec/mspec/lib/mspec/helpers/fixture.rb b/spec/mspec/lib/mspec/helpers/fixture.rb
new file mode 100644
index 0000000000..718c1b7a94
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/fixture.rb
@@ -0,0 +1,26 @@
+class Object
+ # Returns the name of a fixture file by adjoining the directory
+ # of the +file+ argument with "fixtures" and the contents of the
+ # +args+ array. For example,
+ #
+ # +file+ == "some/example_spec.rb"
+ #
+ # and
+ #
+ # +args+ == ["subdir", "file.txt"]
+ #
+ # then the result is the expanded path of
+ #
+ # "some/fixtures/subdir/file.txt".
+ def fixture(file, *args)
+ path = File.dirname(file)
+ path = path[0..-7] if path[-7..-1] == "/shared"
+ fixtures = path[-9..-1] == "/fixtures" ? "" : "fixtures"
+ if File.respond_to?(:realpath)
+ path = File.realpath(path)
+ else
+ path = File.expand_path(path)
+ end
+ File.join(path, fixtures, args)
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/flunk.rb b/spec/mspec/lib/mspec/helpers/flunk.rb
new file mode 100644
index 0000000000..35bd939b85
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/flunk.rb
@@ -0,0 +1,5 @@
+class Object
+ def flunk(msg="This example is a failure")
+ SpecExpectation.fail_with "Failed:", msg
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/fs.rb b/spec/mspec/lib/mspec/helpers/fs.rb
new file mode 100644
index 0000000000..ee33f5fec0
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/fs.rb
@@ -0,0 +1,62 @@
+class Object
+ # Copies a file
+ def cp(source, dest)
+ File.open(dest, "w") do |d|
+ File.open(source, "r") do |s|
+ while data = s.read(1024)
+ d.write data
+ end
+ end
+ end
+ end
+
+ # Creates each directory in path that does not exist.
+ def mkdir_p(path)
+ parts = File.expand_path(path).split %r[/|\\]
+ name = parts.shift
+ parts.each do |part|
+ name = File.join name, part
+
+ if File.file? name
+ raise ArgumentError, "path component of #{path} is a file"
+ end
+
+ Dir.mkdir name unless File.directory? name
+ end
+ end
+
+ # Recursively removes all files and directories in +path+
+ # if +path+ is a directory. Removes the file if +path+ is
+ # a file.
+ def rm_r(*paths)
+ paths.each do |path|
+ path = File.expand_path path
+
+ prefix = SPEC_TEMP_DIR
+ unless path[0, prefix.size] == prefix
+ raise ArgumentError, "#{path} is not prefixed by #{prefix}"
+ end
+
+ # File.symlink? needs to be checked first as
+ # File.exist? returns false for dangling symlinks
+ if File.symlink? path
+ File.unlink path
+ elsif File.directory? path
+ Dir.entries(path).each { |x| rm_r "#{path}/#{x}" unless x =~ /^\.\.?$/ }
+ Dir.rmdir path
+ elsif File.exist? path
+ File.delete path
+ end
+ end
+ end
+
+ # Creates a file +name+. Creates the directory for +name+
+ # if it does not exist.
+ def touch(name, mode="w")
+ mkdir_p File.dirname(name)
+
+ File.open(name, mode) do |f|
+ yield f if block_given?
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/io.rb b/spec/mspec/lib/mspec/helpers/io.rb
new file mode 100644
index 0000000000..83d14441a7
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/io.rb
@@ -0,0 +1,113 @@
+require 'mspec/guards/feature'
+
+class IOStub
+ def initialize
+ @buffer = []
+ @output = ''
+ end
+
+ def write(*str)
+ self << str.join
+ end
+
+ def << str
+ @buffer << str
+ self
+ end
+
+ def print(*str)
+ write(str.join + $\.to_s)
+ end
+
+ def method_missing(name, *args, &block)
+ to_s.send(name, *args, &block)
+ end
+
+ def == other
+ to_s == other
+ end
+
+ def =~ other
+ to_s =~ other
+ end
+
+ def puts(*str)
+ if str.empty?
+ write "\n"
+ else
+ write(str.collect { |s| s.to_s.chomp }.concat([nil]).join("\n"))
+ end
+ end
+
+ def printf(format, *args)
+ self << sprintf(format, *args)
+ end
+
+ def flush
+ @output += @buffer.join('')
+ @buffer.clear
+ self
+ end
+
+ def to_s
+ flush
+ @output
+ end
+
+ alias_method :to_str, :to_s
+
+ def inspect
+ to_s.inspect
+ 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")
+ mode = options_or_mode(mode)
+
+ if mode.kind_of? Hash
+ if mode.key? :mode
+ mode = mode[:mode]
+ else
+ raise ArgumentError, "new_fd options Hash must include :mode"
+ end
+ end
+
+ 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, options_or_mode(mode)), options_or_mode(mode)
+ end
+
+ # 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
+
+ # This helper simplifies passing file access modes or options 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 or option will be returned (i.e. the helper is a no-op).
+ def options_or_mode(oom)
+ return fmode(oom) if oom.kind_of? String
+
+ if FeatureGuard.enabled? :encoding
+ oom
+ else
+ fmode(oom[:mode] || "r:utf-8")
+ end
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/mock_to_path.rb b/spec/mspec/lib/mspec/helpers/mock_to_path.rb
new file mode 100644
index 0000000000..683bb1d9d6
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/mock_to_path.rb
@@ -0,0 +1,8 @@
+class Object
+ def mock_to_path(path)
+ # Cannot use our Object#mock here since it conflicts with RSpec
+ obj = MockObject.new('path')
+ obj.should_receive(:to_path).and_return(path)
+ obj
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/numeric.rb b/spec/mspec/lib/mspec/helpers/numeric.rb
new file mode 100644
index 0000000000..ff30cf2b83
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/numeric.rb
@@ -0,0 +1,72 @@
+require 'mspec/guards/platform'
+
+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.
+ if PlatformGuard.standard? or PlatformGuard.implementation? :topaz
+ if PlatformGuard.wordsize? 32
+ def fixnum_max
+ (2**30) - 1
+ end
+
+ def fixnum_min
+ -(2**30)
+ end
+ elsif PlatformGuard.wordsize? 64
+ def fixnum_max
+ (2**62) - 1
+ end
+
+ def fixnum_min
+ -(2**62)
+ end
+ end
+ elsif PlatformGuard.implementation? :opal
+ def fixnum_max
+ Integer::MAX
+ end
+
+ def fixnum_min
+ Integer::MIN
+ end
+ elsif PlatformGuard.implementation? :rubinius
+ def fixnum_max
+ Fixnum::MAX
+ end
+
+ def fixnum_min
+ Fixnum::MIN
+ end
+ elsif PlatformGuard.implementation?(:jruby) || PlatformGuard.implementation?(:truffleruby)
+ def fixnum_max
+ 9223372036854775807
+ end
+
+ def fixnum_min
+ -9223372036854775808
+ 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
diff --git a/spec/mspec/lib/mspec/helpers/ruby_exe.rb b/spec/mspec/lib/mspec/helpers/ruby_exe.rb
new file mode 100644
index 0000000000..a025be6c81
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/ruby_exe.rb
@@ -0,0 +1,178 @@
+require 'mspec/utils/ruby_name'
+require 'mspec/guards/platform'
+require 'mspec/helpers/tmp'
+
+# The ruby_exe helper provides a wrapper for invoking the
+# same Ruby interpreter with the same falgs 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 three
+# keys: :options, :args and :env. For example:
+#
+# ruby_exe('file.rb', :options => "-w",
+# :args => "> file.txt",
+# :env => { :FOO => "bar" })
+#
+# will be executed as
+#
+# `#{RUBY_EXE} -w #{'file.rb'} > file.txt`
+#
+# with access to ENV["FOO"] with value "bar".
+#
+# If +nil+ is passed for the first argument, the command line
+# will be built only from the options hash.
+#
+# The RUBY_EXE constant is setup by mspec automatically
+# and is used by ruby_exe and ruby_cmd. 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.
+# The flags will then be appended to the resulting value.
+#
+# 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.
+#
+# Additionally, the flags passed to mspec
+# (with -T on the command line or in the config with set :flags)
+# will be appended to RUBY_EXE so that the interpreter
+# is always called with those flags.
+
+class Object
+ def ruby_exe_options(option)
+ case option
+ when :env
+ ENV['RUBY_EXE']
+ when :engine
+ case RUBY_NAME
+ when 'rbx'
+ "bin/rbx"
+ when 'jruby'
+ "bin/jruby"
+ when 'maglev'
+ "maglev-ruby"
+ when 'topaz'
+ "topaz"
+ when 'ironruby'
+ "ir"
+ end
+ when :name
+ require 'rbconfig'
+ bin = RUBY_NAME + (RbConfig::CONFIG['EXEEXT'] || RbConfig::CONFIG['exeext'] || '')
+ File.join(".", bin)
+ when :install_name
+ require 'rbconfig'
+ 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 exe = ruby_exe_options(option)
+
+ if File.file?(exe) and File.executable?(exe)
+ exe = File.expand_path(exe)
+ exe = exe.tr('/', '\\') if PlatformGuard.windows?
+ flags = ENV['RUBY_FLAGS']
+ if flags and !flags.empty?
+ return exe + ' ' + flags
+ else
+ return exe
+ end
+ end
+ end
+ raise Exception, "Unable to find a suitable ruby executable."
+ end
+
+ def ruby_exe(code, opts = {})
+ if opts[:dir]
+ raise "ruby_exe(..., dir: dir) is no longer supported, use Dir.chdir"
+ end
+
+ env = opts[:env] || {}
+ saved_env = {}
+ env.each do |key, value|
+ key = key.to_s
+ saved_env[key] = ENV[key] if ENV.key? key
+ ENV[key] = value
+ end
+
+ escape = opts.delete(:escape)
+ if code and !File.exist?(code) and escape != false
+ tmpfile = tmp("rubyexe.rb")
+ File.open(tmpfile, "w") { |f| f.write(code) }
+ code = tmpfile
+ end
+
+ begin
+ platform_is_not :opal do
+ `#{ruby_cmd(code, opts)}`
+ end
+ ensure
+ saved_env.each { |key, value| ENV[key] = value }
+ env.keys.each do |key|
+ key = key.to_s
+ ENV.delete key unless saved_env.key? key
+ end
+ File.delete tmpfile if tmpfile
+ end
+ end
+
+ def ruby_cmd(code, opts = {})
+ body = code
+
+ if opts[:escape]
+ raise "escape: true is no longer supported in ruby_cmd, use ruby_exe or a fixture"
+ end
+
+ if code and !File.exist?(code)
+ body = "-e #{code.inspect}"
+ end
+
+ [RUBY_EXE, opts[:options], body, opts[:args]].compact.join(' ')
+ end
+
+ unless Object.const_defined?(:RUBY_EXE) and RUBY_EXE
+ RUBY_EXE = resolve_ruby_exe
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/scratch.rb b/spec/mspec/lib/mspec/helpers/scratch.rb
new file mode 100644
index 0000000000..a6b0c02748
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/scratch.rb
@@ -0,0 +1,17 @@
+module ScratchPad
+ def self.clear
+ @record = nil
+ end
+
+ def self.record(arg)
+ @record = arg
+ end
+
+ def self.<<(arg)
+ @record << arg
+ end
+
+ def self.recorded
+ @record
+ end
+end
diff --git a/spec/mspec/lib/mspec/helpers/tmp.rb b/spec/mspec/lib/mspec/helpers/tmp.rb
new file mode 100644
index 0000000000..742eb57fdc
--- /dev/null
+++ b/spec/mspec/lib/mspec/helpers/tmp.rb
@@ -0,0 +1,45 @@
+# 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(ENV["SPEC_TEMP_DIR"] || "rubyspec_temp")
+
+SPEC_TEMP_UNIQUIFIER = "0"
+
+SPEC_TEMP_DIR_PID = Process.pid
+
+at_exit do
+ begin
+ if SPEC_TEMP_DIR_PID == Process.pid
+ Dir.delete SPEC_TEMP_DIR if File.directory? SPEC_TEMP_DIR
+ end
+ rescue SystemCallError
+ STDERR.puts <<-EOM
+
+-----------------------------------------------------
+The rubyspec temp directory is not empty. Ensure that
+all specs are cleaning up temporary files:
+ #{SPEC_TEMP_DIR}
+-----------------------------------------------------
+
+ 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 Dir.exist? 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