diff options
Diffstat (limited to 'spec/mspec/lib/mspec/helpers')
-rw-r--r-- | spec/mspec/lib/mspec/helpers/argf.rb | 37 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/argv.rb | 46 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/datetime.rb | 51 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/fixture.rb | 26 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/flunk.rb | 5 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/fs.rb | 62 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/io.rb | 113 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/mock_to_path.rb | 8 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/numeric.rb | 72 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/ruby_exe.rb | 178 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/scratch.rb | 17 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/helpers/tmp.rb | 45 |
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 |