mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Add in-tree mspec and ruby/spec
* For easier modifications of ruby/spec by MRI developers. * .gitignore: track changes under spec. * spec/mspec, spec/rubyspec: add in-tree mspec and ruby/spec. These files can therefore be updated like any other file in MRI. Instructions are provided in spec/README. [Feature #13156] [ruby-core:79246] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@58595 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
ed7d803500
commit
95e8c48dd3
4645 changed files with 230678 additions and 4 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -172,10 +172,6 @@ y.tab.c
|
|||
/gems/*.gem
|
||||
/gems/*-*
|
||||
|
||||
# /spec/
|
||||
/spec/mspec
|
||||
/spec/rubyspec
|
||||
|
||||
# /tool/
|
||||
/tool/config.guess
|
||||
/tool/config.sub
|
||||
|
|
26
spec/mspec/.gitignore
vendored
Normal file
26
spec/mspec/.gitignore
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
pkg
|
||||
*.rbc
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
*.sw?
|
||||
|
||||
.rbx
|
||||
|
||||
# ctags dir
|
||||
/tags
|
||||
|
||||
*.gem
|
||||
.bundle
|
||||
.config
|
||||
.yardoc
|
||||
InstalledFiles
|
||||
_yardoc
|
||||
coverage
|
||||
doc/
|
||||
lib/bundler/man
|
||||
rdoc
|
||||
spec/reports
|
||||
test/tmp
|
||||
test/version_tmp
|
||||
tmp
|
9
spec/mspec/.travis.yml
Normal file
9
spec/mspec/.travis.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
sudo: false
|
||||
language: ruby
|
||||
script:
|
||||
- bundle exec rspec
|
||||
rvm:
|
||||
- 2.2.7
|
||||
- 2.3.4
|
||||
- 2.4.1
|
||||
- ruby-head
|
4
spec/mspec/Gemfile
Normal file
4
spec/mspec/Gemfile
Normal file
|
@ -0,0 +1,4 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
# Specify your gem's dependencies in mspec.gemspec
|
||||
gemspec
|
30
spec/mspec/Gemfile.lock
Normal file
30
spec/mspec/Gemfile.lock
Normal file
|
@ -0,0 +1,30 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
mspec (1.8.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
diff-lcs (1.2.5)
|
||||
rake (10.4.2)
|
||||
rspec (2.14.1)
|
||||
rspec-core (~> 2.14.0)
|
||||
rspec-expectations (~> 2.14.0)
|
||||
rspec-mocks (~> 2.14.0)
|
||||
rspec-core (2.14.8)
|
||||
rspec-expectations (2.14.5)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rspec-mocks (2.14.6)
|
||||
|
||||
PLATFORMS
|
||||
java
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
mspec!
|
||||
rake (~> 10.0)
|
||||
rspec (~> 2.14.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.10.2
|
22
spec/mspec/LICENSE
Normal file
22
spec/mspec/LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
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.
|
88
spec/mspec/README.md
Normal file
88
spec/mspec/README.md
Normal file
|
@ -0,0 +1,88 @@
|
|||
[![Build Status](https://travis-ci.org/ruby/mspec.svg?branch=master)](https://travis-ci.org/ruby/mspec)
|
||||
|
||||
## Overview
|
||||
|
||||
MSpec is a specialized framework that is syntax-compatible with RSpec for
|
||||
basic things like 'describe', 'it' blocks and 'before', 'after' actions. MSpec
|
||||
contains additional features that assist in writing the RubySpecs used by
|
||||
multiple Ruby implementations.
|
||||
|
||||
MSpec attempts to use the simplest Ruby language features so that beginning
|
||||
Ruby implementations can run the Ruby specs.
|
||||
|
||||
MSpec is not intended as a replacement for RSpec. MSpec attempts to provide a
|
||||
subset of RSpec's features in some cases and a superset in others. It does not
|
||||
provide all the matchers, for instance.
|
||||
|
||||
However, MSpec provides several extensions to facilitate writing the Ruby
|
||||
specs in a manner compatible with multiple Ruby implementations.
|
||||
|
||||
1. MSpec offers a set of guards to control execution of the specs. These
|
||||
guards not only enable or disable execution but also annotate the specs
|
||||
with additional information about why they are run or not run.
|
||||
|
||||
2. MSpec provides a different shared spec implementation specifically
|
||||
designed to ease writing specs for the numerous aliased methods in Ruby.
|
||||
The MSpec shared spec implementation should not conflict with RSpec's own
|
||||
shared behavior facility.
|
||||
|
||||
3. MSpec provides various helper methods to simplify some specs, for
|
||||
example, creating temporary file names.
|
||||
|
||||
4. MSpec has several specialized runner scripts that includes a
|
||||
configuration facility with a default project file and user-specific
|
||||
overrides.
|
||||
|
||||
|
||||
## Bundler
|
||||
|
||||
A Gemfile is provided. Use Bundler to install gem dependencies. To install
|
||||
Bundler, run the following:
|
||||
|
||||
```bash
|
||||
gem install bundler
|
||||
```
|
||||
|
||||
To install the gem dependencies with Bundler, run the following:
|
||||
|
||||
```bash
|
||||
ruby -S bundle install
|
||||
```
|
||||
|
||||
## Running Specs
|
||||
|
||||
Use RSpec to run the MSpec specs. There are no plans currently to make the
|
||||
MSpec specs runnable by MSpec.
|
||||
|
||||
After installing the gem dependencies, the specs can be run as follows:
|
||||
|
||||
```bash
|
||||
ruby -S bundle exec rspec
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```bash
|
||||
ruby -S rake
|
||||
```
|
||||
|
||||
To run an individual spec file, use the following example:
|
||||
|
||||
```bash
|
||||
ruby -S bundle exec rspec spec/helpers/ruby_exe_spec.rb
|
||||
```
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
See http://ruby.github.io/rubyspec.github.io/
|
||||
|
||||
|
||||
## Source Code
|
||||
|
||||
See https://github.com/ruby/mspec
|
||||
|
||||
|
||||
## License
|
||||
|
||||
See the LICENSE in the source code.
|
7
spec/mspec/Rakefile
Normal file
7
spec/mspec/Rakefile
Normal file
|
@ -0,0 +1,7 @@
|
|||
require 'bundler/gem_tasks'
|
||||
require 'bundler/setup'
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
task :default => :spec
|
7
spec/mspec/bin/mkspec
Executable file
7
spec/mspec/bin/mkspec
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift File.expand_path('../../lib', __FILE__)
|
||||
|
||||
require 'mspec/commands/mkspec'
|
||||
|
||||
MkSpec.main
|
1
spec/mspec/bin/mkspec.bat
Executable file
1
spec/mspec/bin/mkspec.bat
Executable file
|
@ -0,0 +1 @@
|
|||
@"ruby.exe" "%~dpn0" %*
|
7
spec/mspec/bin/mspec
Executable file
7
spec/mspec/bin/mspec
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift File.expand_path('../../lib', __FILE__)
|
||||
|
||||
require 'mspec/commands/mspec'
|
||||
|
||||
MSpecMain.main
|
7
spec/mspec/bin/mspec-ci
Executable file
7
spec/mspec/bin/mspec-ci
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift File.expand_path('../../lib', __FILE__)
|
||||
|
||||
require 'mspec/commands/mspec-ci'
|
||||
|
||||
MSpecCI.main
|
1
spec/mspec/bin/mspec-ci.bat
Executable file
1
spec/mspec/bin/mspec-ci.bat
Executable file
|
@ -0,0 +1 @@
|
|||
@"ruby.exe" "%~dpn0" %*
|
7
spec/mspec/bin/mspec-run
Executable file
7
spec/mspec/bin/mspec-run
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift File.expand_path('../../lib', __FILE__)
|
||||
|
||||
require 'mspec/commands/mspec-run'
|
||||
|
||||
MSpecRun.main
|
1
spec/mspec/bin/mspec-run.bat
Executable file
1
spec/mspec/bin/mspec-run.bat
Executable file
|
@ -0,0 +1 @@
|
|||
@"ruby.exe" "%~dpn0" %*
|
7
spec/mspec/bin/mspec-tag
Executable file
7
spec/mspec/bin/mspec-tag
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift File.expand_path('../../lib', __FILE__)
|
||||
|
||||
require 'mspec/commands/mspec-tag'
|
||||
|
||||
MSpecTag.main
|
1
spec/mspec/bin/mspec-tag.bat
Executable file
1
spec/mspec/bin/mspec-tag.bat
Executable file
|
@ -0,0 +1 @@
|
|||
@"ruby.exe" "%~dpn0" %*
|
1
spec/mspec/bin/mspec.bat
Executable file
1
spec/mspec/bin/mspec.bat
Executable file
|
@ -0,0 +1 @@
|
|||
@"ruby.exe" "%~dpn0" %*
|
20
spec/mspec/lib/mspec.rb
Normal file
20
spec/mspec/lib/mspec.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
require 'mspec/matchers'
|
||||
require 'mspec/expectations'
|
||||
require 'mspec/mocks'
|
||||
require 'mspec/runner'
|
||||
require 'mspec/guards'
|
||||
require 'mspec/helpers'
|
||||
require 'mspec/version'
|
||||
|
||||
# If the implementation on which the specs are run cannot
|
||||
# load pp from the standard library, add a pp.rb file that
|
||||
# defines the #pretty_inspect method on Object or Kernel.
|
||||
begin
|
||||
require 'pp'
|
||||
rescue LoadError
|
||||
module Kernel
|
||||
def pretty_inspect
|
||||
inspect
|
||||
end
|
||||
end
|
||||
end
|
155
spec/mspec/lib/mspec/commands/mkspec.rb
Executable file
155
spec/mspec/lib/mspec/commands/mkspec.rb
Executable file
|
@ -0,0 +1,155 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rbconfig'
|
||||
require 'mspec/version'
|
||||
require 'mspec/utils/options'
|
||||
require 'mspec/utils/name_map'
|
||||
require 'mspec/helpers/fs'
|
||||
|
||||
class MkSpec
|
||||
attr_reader :config
|
||||
|
||||
def initialize
|
||||
@config = {
|
||||
:constants => [],
|
||||
:requires => [],
|
||||
:base => "core",
|
||||
:version => nil
|
||||
}
|
||||
@map = NameMap.new true
|
||||
end
|
||||
|
||||
def options(argv=ARGV)
|
||||
options = MSpecOptions.new "mkspec [options]", 32
|
||||
|
||||
options.on("-c", "--constant", "CONSTANT",
|
||||
"Class or Module to generate spec stubs for") do |name|
|
||||
config[:constants] << name
|
||||
end
|
||||
options.on("-b", "--base", "DIR",
|
||||
"Directory to generate specs into") do |directory|
|
||||
config[:base] = File.expand_path directory
|
||||
end
|
||||
options.on("-r", "--require", "LIBRARY",
|
||||
"A library to require") do |file|
|
||||
config[:requires] << file
|
||||
end
|
||||
options.on("-V", "--version-guard", "VERSION",
|
||||
"Specify version for ruby_version_is guards") do |version|
|
||||
config[:version] = version
|
||||
end
|
||||
options.version MSpec::VERSION
|
||||
options.help
|
||||
|
||||
options.doc "\n How might this work in the real world?\n"
|
||||
options.doc " 1. To create spec stubs for every class or module in Object\n"
|
||||
options.doc " $ mkspec\n"
|
||||
options.doc " 2. To create spec stubs for Fixnum\n"
|
||||
options.doc " $ mkspec -c Fixnum\n"
|
||||
options.doc " 3. To create spec stubs for Complex in 'superspec/complex'\n"
|
||||
options.doc " $ mkspec -c Complex -r complex -b superspec"
|
||||
options.doc ""
|
||||
|
||||
options.parse argv
|
||||
end
|
||||
|
||||
def create_directory(mod)
|
||||
subdir = @map.dir_name mod, config[:base]
|
||||
|
||||
if File.exist? subdir
|
||||
unless File.directory? subdir
|
||||
puts "#{subdir} already exists and is not a directory."
|
||||
return nil
|
||||
end
|
||||
else
|
||||
mkdir_p subdir
|
||||
end
|
||||
|
||||
subdir
|
||||
end
|
||||
|
||||
def write_requires(dir, file)
|
||||
prefix = config[:base] + '/'
|
||||
raise dir unless dir.start_with? prefix
|
||||
sub = dir[prefix.size..-1]
|
||||
parents = '../' * (sub.split('/').length + 1)
|
||||
|
||||
File.open(file, 'w') do |f|
|
||||
f.puts "require File.expand_path('../#{parents}spec_helper', __FILE__)"
|
||||
config[:requires].each do |lib|
|
||||
f.puts "require '#{lib}'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_version(f)
|
||||
f.puts ""
|
||||
if version = config[:version]
|
||||
f.puts "ruby_version_is #{version} do"
|
||||
yield " "
|
||||
f.puts "end"
|
||||
else
|
||||
yield ""
|
||||
end
|
||||
end
|
||||
|
||||
def write_spec(file, meth, exists)
|
||||
if exists
|
||||
out = `#{ruby} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e '#{meth}' #{file}`
|
||||
return if out.include?(meth)
|
||||
end
|
||||
|
||||
File.open file, 'a' do |f|
|
||||
write_version(f) do |indent|
|
||||
f.puts <<-EOS
|
||||
#{indent}describe "#{meth}" do
|
||||
#{indent} it "needs to be reviewed for spec completeness"
|
||||
#{indent}end
|
||||
EOS
|
||||
end
|
||||
end
|
||||
|
||||
puts file
|
||||
end
|
||||
|
||||
def create_file(dir, mod, meth, name)
|
||||
file = File.join dir, @map.file_name(meth, mod)
|
||||
exists = File.exist? file
|
||||
|
||||
write_requires dir, file unless exists
|
||||
write_spec file, name, exists
|
||||
end
|
||||
|
||||
def run
|
||||
config[:requires].each { |lib| require lib }
|
||||
constants = config[:constants]
|
||||
constants = Object.constants if constants.empty?
|
||||
|
||||
@map.map({}, constants).each do |mod, methods|
|
||||
name = mod.chop
|
||||
next unless dir = create_directory(name)
|
||||
|
||||
methods.each { |method| create_file dir, name, method, mod + method }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Determine and return the path of the ruby executable.
|
||||
|
||||
def ruby
|
||||
ruby = File.join(RbConfig::CONFIG['bindir'],
|
||||
RbConfig::CONFIG['ruby_install_name'])
|
||||
|
||||
ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR
|
||||
|
||||
return ruby
|
||||
end
|
||||
|
||||
def self.main
|
||||
ENV['MSPEC_RUNNER'] = '1'
|
||||
|
||||
script = new
|
||||
script.options
|
||||
script.run
|
||||
end
|
||||
end
|
79
spec/mspec/lib/mspec/commands/mspec-ci.rb
Normal file
79
spec/mspec/lib/mspec/commands/mspec-ci.rb
Normal file
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require 'mspec/version'
|
||||
require 'mspec/utils/options'
|
||||
require 'mspec/utils/script'
|
||||
|
||||
|
||||
class MSpecCI < MSpecScript
|
||||
def options(argv=ARGV)
|
||||
options = MSpecOptions.new "mspec ci [options] (FILE|DIRECTORY|GLOB)+", 30, config
|
||||
|
||||
options.doc " Ask yourself:"
|
||||
options.doc " 1. How to run the specs?"
|
||||
options.doc " 2. How to modify the guard behavior?"
|
||||
options.doc " 2. How to display the output?"
|
||||
options.doc " 3. What action to perform?"
|
||||
options.doc " 4. When to perform it?"
|
||||
|
||||
options.doc "\n How to run the specs"
|
||||
options.chdir
|
||||
options.prefix
|
||||
options.configure { |f| load f }
|
||||
options.name
|
||||
options.pretend
|
||||
options.interrupt
|
||||
|
||||
options.doc "\n How to modify the guard behavior"
|
||||
options.unguarded
|
||||
options.verify
|
||||
|
||||
options.doc "\n How to display their output"
|
||||
options.formatters
|
||||
options.verbose
|
||||
|
||||
options.doc "\n What action to perform"
|
||||
options.actions
|
||||
|
||||
options.doc "\n When to perform it"
|
||||
options.action_filters
|
||||
|
||||
options.doc "\n Help!"
|
||||
options.debug
|
||||
options.version MSpec::VERSION
|
||||
options.help
|
||||
|
||||
options.doc "\n Custom options"
|
||||
custom_options options
|
||||
|
||||
options.doc "\n How might this work in the real world?"
|
||||
options.doc "\n 1. To simply run the known good specs"
|
||||
options.doc "\n $ mspec ci"
|
||||
options.doc "\n 2. To run a subset of the known good specs"
|
||||
options.doc "\n $ mspec ci path/to/specs"
|
||||
options.doc "\n 3. To start the debugger before the spec matching 'this crashes'"
|
||||
options.doc "\n $ mspec ci --spec-debug -S 'this crashes'"
|
||||
options.doc ""
|
||||
|
||||
patterns = options.parse argv
|
||||
patterns = config[:ci_files] if patterns.empty?
|
||||
@files = files patterns
|
||||
end
|
||||
|
||||
def run
|
||||
MSpec.register_tags_patterns config[:tags_patterns]
|
||||
MSpec.register_files @files
|
||||
|
||||
tags = ["fails", "critical", "unstable", "incomplete", "unsupported"]
|
||||
tags += Array(config[:ci_xtags])
|
||||
|
||||
require 'mspec/runner/filters/tag'
|
||||
filter = TagFilter.new(:exclude, *tags)
|
||||
filter.register
|
||||
|
||||
MSpec.process
|
||||
exit MSpec.exit_code
|
||||
end
|
||||
end
|
87
spec/mspec/lib/mspec/commands/mspec-run.rb
Normal file
87
spec/mspec/lib/mspec/commands/mspec-run.rb
Normal file
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require 'mspec/version'
|
||||
require 'mspec/utils/options'
|
||||
require 'mspec/utils/script'
|
||||
|
||||
|
||||
class MSpecRun < MSpecScript
|
||||
def initialize
|
||||
super
|
||||
|
||||
config[:files] = []
|
||||
end
|
||||
|
||||
def options(argv=ARGV)
|
||||
options = MSpecOptions.new "mspec run [options] (FILE|DIRECTORY|GLOB)+", 30, config
|
||||
|
||||
options.doc " Ask yourself:"
|
||||
options.doc " 1. What specs to run?"
|
||||
options.doc " 2. How to modify the execution?"
|
||||
options.doc " 3. How to modify the guard behavior?"
|
||||
options.doc " 4. How to display the output?"
|
||||
options.doc " 5. What action to perform?"
|
||||
options.doc " 6. When to perform it?"
|
||||
|
||||
options.doc "\n What specs to run"
|
||||
options.filters
|
||||
|
||||
options.doc "\n How to modify the execution"
|
||||
options.chdir
|
||||
options.prefix
|
||||
options.configure { |f| load f }
|
||||
options.name
|
||||
options.randomize
|
||||
options.repeat
|
||||
options.pretend
|
||||
options.interrupt
|
||||
|
||||
options.doc "\n How to modify the guard behavior"
|
||||
options.unguarded
|
||||
options.verify
|
||||
|
||||
options.doc "\n How to display their output"
|
||||
options.formatters
|
||||
options.verbose
|
||||
|
||||
options.doc "\n What action to perform"
|
||||
options.actions
|
||||
|
||||
options.doc "\n When to perform it"
|
||||
options.action_filters
|
||||
|
||||
options.doc "\n Help!"
|
||||
options.debug
|
||||
options.version MSpec::VERSION
|
||||
options.help
|
||||
|
||||
options.doc "\n Custom options"
|
||||
custom_options options
|
||||
|
||||
options.doc "\n How might this work in the real world?"
|
||||
options.doc "\n 1. To simply run some specs"
|
||||
options.doc "\n $ mspec path/to/the/specs"
|
||||
options.doc " mspec path/to/the_file_spec.rb"
|
||||
options.doc "\n 2. To run specs tagged with 'fails'"
|
||||
options.doc "\n $ mspec -g fails path/to/the_file_spec.rb"
|
||||
options.doc "\n 3. To start the debugger before the spec matching 'this crashes'"
|
||||
options.doc "\n $ mspec --spec-debug -S 'this crashes' path/to/the_file_spec.rb"
|
||||
options.doc "\n 4. To run some specs matching 'this crashes'"
|
||||
options.doc "\n $ mspec -e 'this crashes' path/to/the_file_spec.rb"
|
||||
|
||||
options.doc ""
|
||||
|
||||
patterns = options.parse argv
|
||||
@files = files_from_patterns(patterns)
|
||||
end
|
||||
|
||||
def run
|
||||
MSpec.register_tags_patterns config[:tags_patterns]
|
||||
MSpec.register_files @files
|
||||
|
||||
MSpec.process
|
||||
exit MSpec.exit_code
|
||||
end
|
||||
end
|
133
spec/mspec/lib/mspec/commands/mspec-tag.rb
Normal file
133
spec/mspec/lib/mspec/commands/mspec-tag.rb
Normal file
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'mspec/version'
|
||||
require 'mspec/utils/options'
|
||||
require 'mspec/utils/script'
|
||||
|
||||
|
||||
class MSpecTag < MSpecScript
|
||||
def initialize
|
||||
super
|
||||
|
||||
config[:tagger] = :add
|
||||
config[:tag] = 'fails:'
|
||||
config[:outcome] = :fail
|
||||
config[:ltags] = []
|
||||
end
|
||||
|
||||
def options(argv=ARGV)
|
||||
options = MSpecOptions.new "mspec tag [options] (FILE|DIRECTORY|GLOB)+", 30, config
|
||||
|
||||
options.doc " Ask yourself:"
|
||||
options.doc " 1. What specs to run?"
|
||||
options.doc " 2. How to modify the execution?"
|
||||
options.doc " 3. How to display the output?"
|
||||
options.doc " 4. What tag action to perform?"
|
||||
options.doc " 5. When to perform it?"
|
||||
|
||||
options.doc "\n What specs to run"
|
||||
options.filters
|
||||
|
||||
options.doc "\n How to modify the execution"
|
||||
options.configure { |f| load f }
|
||||
options.name
|
||||
options.pretend
|
||||
options.unguarded
|
||||
options.interrupt
|
||||
|
||||
options.doc "\n How to display their output"
|
||||
options.formatters
|
||||
options.verbose
|
||||
|
||||
options.doc "\n What action to perform and when to perform it"
|
||||
options.on("-N", "--add", "TAG",
|
||||
"Add TAG with format 'tag' or 'tag(comment)' (see -Q, -F, -L)") do |o|
|
||||
config[:tagger] = :add
|
||||
config[:tag] = "#{o}:"
|
||||
end
|
||||
options.on("-R", "--del", "TAG",
|
||||
"Delete TAG (see -Q, -F, -L)") do |o|
|
||||
config[:tagger] = :del
|
||||
config[:tag] = "#{o}:"
|
||||
config[:outcome] = :pass
|
||||
end
|
||||
options.on("-Q", "--pass", "Apply action to specs that pass (default for --del)") do
|
||||
config[:outcome] = :pass
|
||||
end
|
||||
options.on("-F", "--fail", "Apply action to specs that fail (default for --add)") do
|
||||
config[:outcome] = :fail
|
||||
end
|
||||
options.on("-L", "--all", "Apply action to all specs") do
|
||||
config[:outcome] = :all
|
||||
end
|
||||
options.on("--list", "TAG", "Display descriptions of any specs tagged with TAG") do |t|
|
||||
config[:tagger] = :list
|
||||
config[:ltags] << t
|
||||
end
|
||||
options.on("--list-all", "Display descriptions of any tagged specs") do
|
||||
config[:tagger] = :list_all
|
||||
end
|
||||
options.on("--purge", "Remove all tags not matching any specs") do
|
||||
config[:tagger] = :purge
|
||||
end
|
||||
|
||||
options.doc "\n Help!"
|
||||
options.debug
|
||||
options.version MSpec::VERSION
|
||||
options.help
|
||||
|
||||
options.doc "\n Custom options"
|
||||
custom_options options
|
||||
|
||||
options.doc "\n How might this work in the real world?"
|
||||
options.doc "\n 1. To add the 'fails' tag to failing specs"
|
||||
options.doc "\n $ mspec tag path/to/the_file_spec.rb"
|
||||
options.doc "\n 2. To remove the 'fails' tag from passing specs"
|
||||
options.doc "\n $ mspec tag --del fails path/to/the_file_spec.rb"
|
||||
options.doc "\n 3. To display the descriptions for all specs tagged with 'fails'"
|
||||
options.doc "\n $ mspec tag --list fails path/to/the/specs"
|
||||
options.doc ""
|
||||
|
||||
patterns = options.parse argv
|
||||
if patterns.empty?
|
||||
puts options
|
||||
puts "No files specified."
|
||||
exit 1
|
||||
end
|
||||
@files = files patterns
|
||||
end
|
||||
|
||||
def register
|
||||
require 'mspec/runner/actions'
|
||||
|
||||
case config[:tagger]
|
||||
when :add, :del
|
||||
tag = SpecTag.new config[:tag]
|
||||
tagger = TagAction.new(config[:tagger], config[:outcome], tag.tag, tag.comment,
|
||||
config[:atags], config[:astrings])
|
||||
when :list, :list_all
|
||||
tagger = TagListAction.new config[:tagger] == :list_all ? nil : config[:ltags]
|
||||
MSpec.register_mode :pretend
|
||||
config[:formatter] = false
|
||||
when :purge
|
||||
tagger = TagPurgeAction.new
|
||||
MSpec.register_mode :pretend
|
||||
MSpec.register_mode :unguarded
|
||||
config[:formatter] = false
|
||||
else
|
||||
raise ArgumentError, "No recognized action given"
|
||||
end
|
||||
tagger.register
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def run
|
||||
MSpec.register_tags_patterns config[:tags_patterns]
|
||||
MSpec.register_files @files
|
||||
|
||||
MSpec.process
|
||||
exit MSpec.exit_code
|
||||
end
|
||||
end
|
||||
|
163
spec/mspec/lib/mspec/commands/mspec.rb
Executable file
163
spec/mspec/lib/mspec/commands/mspec.rb
Executable file
|
@ -0,0 +1,163 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'mspec/version'
|
||||
require 'mspec/utils/options'
|
||||
require 'mspec/utils/script'
|
||||
require 'mspec/helpers/tmp'
|
||||
require 'mspec/runner/actions/filter'
|
||||
require 'mspec/runner/actions/timer'
|
||||
|
||||
|
||||
class MSpecMain < MSpecScript
|
||||
def initialize
|
||||
super
|
||||
|
||||
config[:loadpath] = []
|
||||
config[:requires] = []
|
||||
config[:target] = ENV['RUBY'] || 'ruby'
|
||||
config[:flags] = []
|
||||
config[:command] = nil
|
||||
config[:options] = []
|
||||
config[:launch] = []
|
||||
end
|
||||
|
||||
def options(argv=ARGV)
|
||||
config[:command] = argv.shift if ["ci", "run", "tag"].include?(argv[0])
|
||||
|
||||
options = MSpecOptions.new "mspec [COMMAND] [options] (FILE|DIRECTORY|GLOB)+", 30, config
|
||||
|
||||
options.doc " The mspec command sets up and invokes the sub-commands"
|
||||
options.doc " (see below) to enable, for instance, running the specs"
|
||||
options.doc " with different implementations like ruby, jruby, rbx, etc.\n"
|
||||
|
||||
options.configure do |f|
|
||||
load f
|
||||
config[:options] << '-B' << f
|
||||
end
|
||||
|
||||
options.targets
|
||||
|
||||
options.on("--warnings", "Don't supress warnings") do
|
||||
config[:flags] << '-w'
|
||||
ENV['OUTPUT_WARNINGS'] = '1'
|
||||
end
|
||||
|
||||
options.on("-j", "--multi", "Run multiple (possibly parallel) subprocesses") do
|
||||
config[:multi] = true
|
||||
config[:options] << "-fy"
|
||||
end
|
||||
|
||||
options.version MSpec::VERSION do
|
||||
if config[:command]
|
||||
config[:options] << "-v"
|
||||
else
|
||||
puts "#{File.basename $0} #{MSpec::VERSION}"
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
options.help do
|
||||
if config[:command]
|
||||
config[:options] << "-h"
|
||||
else
|
||||
puts options
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
options.doc "\n Custom options"
|
||||
custom_options options
|
||||
|
||||
# The rest of the help output
|
||||
options.doc "\n where COMMAND is one of:\n"
|
||||
options.doc " run - Run the specified specs (default)"
|
||||
options.doc " ci - Run the known good specs"
|
||||
options.doc " tag - Add or remove tags\n"
|
||||
options.doc " mspec COMMAND -h for more options\n"
|
||||
options.doc " example: $ mspec run -h\n"
|
||||
|
||||
options.on_extra { |o| config[:options] << o }
|
||||
options.parse(argv)
|
||||
|
||||
if config[:multi]
|
||||
options = MSpecOptions.new "mspec", 30, config
|
||||
options.all
|
||||
patterns = options.parse(config[:options])
|
||||
@files = files_from_patterns(patterns)
|
||||
end
|
||||
end
|
||||
|
||||
def register; end
|
||||
|
||||
def multi_exec(argv)
|
||||
MSpec.register_files @files
|
||||
|
||||
require 'mspec/runner/formatters/multi'
|
||||
formatter = MultiFormatter.new
|
||||
|
||||
output_files = []
|
||||
processes = [cores, @files.size].min
|
||||
children = processes.times.map { |i|
|
||||
name = tmp "mspec-multi-#{i}"
|
||||
output_files << name
|
||||
|
||||
env = {
|
||||
"SPEC_TEMP_DIR" => "rubyspec_temp_#{i}",
|
||||
"MSPEC_MULTI" => i.to_s
|
||||
}
|
||||
command = argv + ["-o", name]
|
||||
$stderr.puts "$ #{command.join(' ')}" if $MSPEC_DEBUG
|
||||
IO.popen([env, *command], "rb+")
|
||||
}
|
||||
|
||||
puts children.map { |child| child.gets }.uniq
|
||||
formatter.start
|
||||
|
||||
until @files.empty?
|
||||
IO.select(children)[0].each { |io|
|
||||
reply = io.read(1)
|
||||
case reply
|
||||
when '.'
|
||||
formatter.unload
|
||||
when nil
|
||||
raise "Worker died!"
|
||||
else
|
||||
while chunk = (io.read_nonblock(4096) rescue nil)
|
||||
reply += chunk
|
||||
end
|
||||
raise reply
|
||||
end
|
||||
io.puts @files.shift unless @files.empty?
|
||||
}
|
||||
end
|
||||
|
||||
ok = true
|
||||
children.each { |child|
|
||||
child.puts "QUIT"
|
||||
Process.wait(child.pid)
|
||||
ok &&= $?.success?
|
||||
}
|
||||
|
||||
formatter.aggregate_results(output_files)
|
||||
formatter.finish
|
||||
ok
|
||||
end
|
||||
|
||||
def run
|
||||
argv = config[:target].split(/\s+/)
|
||||
|
||||
argv.concat config[:launch]
|
||||
argv.concat config[:flags]
|
||||
argv.concat config[:loadpath]
|
||||
argv.concat config[:requires]
|
||||
argv << "#{MSPEC_HOME}/bin/mspec-#{ config[:command] || "run" }"
|
||||
argv.concat config[:options]
|
||||
|
||||
if config[:multi]
|
||||
exit multi_exec(argv)
|
||||
else
|
||||
$stderr.puts "$ #{argv.join(' ')}"
|
||||
exec(*argv)
|
||||
end
|
||||
end
|
||||
end
|
2
spec/mspec/lib/mspec/expectations.rb
Normal file
2
spec/mspec/lib/mspec/expectations.rb
Normal file
|
@ -0,0 +1,2 @@
|
|||
require 'mspec/expectations/expectations'
|
||||
require 'mspec/expectations/should'
|
21
spec/mspec/lib/mspec/expectations/expectations.rb
Normal file
21
spec/mspec/lib/mspec/expectations/expectations.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
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)
|
||||
expected_to_s = expected.to_s
|
||||
actual_to_s = actual.to_s
|
||||
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
|
29
spec/mspec/lib/mspec/expectations/should.rb
Normal file
29
spec/mspec/lib/mspec/expectations/should.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
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
|
||||
expected, actual = matcher.failure_message
|
||||
SpecExpectation.fail_with(expected, actual)
|
||||
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
|
||||
expected, actual = matcher.negative_failure_message
|
||||
SpecExpectation.fail_with(expected, actual)
|
||||
end
|
||||
else
|
||||
SpecNegativeOperatorMatcher.new(self)
|
||||
end
|
||||
end
|
||||
end
|
12
spec/mspec/lib/mspec/guards.rb
Normal file
12
spec/mspec/lib/mspec/guards.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
require 'mspec/utils/ruby_name'
|
||||
require 'mspec/guards/block_device'
|
||||
require 'mspec/guards/bug'
|
||||
require 'mspec/guards/conflict'
|
||||
require 'mspec/guards/endian'
|
||||
require 'mspec/guards/feature'
|
||||
require 'mspec/guards/guard'
|
||||
require 'mspec/guards/platform'
|
||||
require 'mspec/guards/quarantine'
|
||||
require 'mspec/guards/support'
|
||||
require 'mspec/guards/superuser'
|
||||
require 'mspec/guards/version'
|
18
spec/mspec/lib/mspec/guards/block_device.rb
Normal file
18
spec/mspec/lib/mspec/guards/block_device.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
require 'mspec/guards/guard'
|
||||
|
||||
class BlockDeviceGuard < SpecGuard
|
||||
def match?
|
||||
platform_is_not :freebsd, :windows, :opal do
|
||||
block = `find /dev /devices -type b 2> /dev/null`
|
||||
return !(block.nil? || block.empty?)
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def with_block_device(&block)
|
||||
BlockDeviceGuard.new.run_if(:with_block_device, &block)
|
||||
end
|
||||
end
|
30
spec/mspec/lib/mspec/guards/bug.rb
Normal file
30
spec/mspec/lib/mspec/guards/bug.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
require 'mspec/guards/version'
|
||||
|
||||
class BugGuard < VersionGuard
|
||||
def initialize(bug, version)
|
||||
@bug = bug
|
||||
if String === version
|
||||
MSpec.deprecate "ruby_bug with a single version", 'an exclusive range ("2.1"..."2.3")'
|
||||
@version = SpecVersion.new version, true
|
||||
else
|
||||
super(version)
|
||||
end
|
||||
@parameters = [@bug, @version]
|
||||
end
|
||||
|
||||
def match?
|
||||
return false if MSpec.mode? :no_ruby_bug
|
||||
return false unless PlatformGuard.standard?
|
||||
if Range === @version
|
||||
super
|
||||
else
|
||||
FULL_RUBY_VERSION <= @version
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def ruby_bug(bug, version, &block)
|
||||
BugGuard.new(bug, version).run_unless(:ruby_bug, &block)
|
||||
end
|
||||
end
|
19
spec/mspec/lib/mspec/guards/conflict.rb
Normal file
19
spec/mspec/lib/mspec/guards/conflict.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
require 'mspec/guards/guard'
|
||||
|
||||
class ConflictsGuard < SpecGuard
|
||||
def match?
|
||||
# Always convert constants to symbols regardless of version.
|
||||
constants = Object.constants.map { |x| x.to_sym }
|
||||
@parameters.any? { |mod| constants.include? mod }
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
# In some cases, libraries will modify another Ruby method's
|
||||
# behavior. The specs for the method's behavior will then fail
|
||||
# if that library is loaded. This guard will not run if any of
|
||||
# the specified constants exist in Object.constants.
|
||||
def conflicts_with(*modules, &block)
|
||||
ConflictsGuard.new(*modules).run_unless(:conflicts_with, &block)
|
||||
end
|
||||
end
|
27
spec/mspec/lib/mspec/guards/endian.rb
Normal file
27
spec/mspec/lib/mspec/guards/endian.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
require 'mspec/guards/guard'
|
||||
|
||||
# Despite that these are inverses, the two classes are
|
||||
# used to simplify MSpec guard reporting modes
|
||||
|
||||
class EndianGuard < SpecGuard
|
||||
def pattern
|
||||
@pattern ||= [1].pack('L')
|
||||
end
|
||||
private :pattern
|
||||
end
|
||||
|
||||
class BigEndianGuard < EndianGuard
|
||||
def match?
|
||||
pattern[-1] == ?\001
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def big_endian(&block)
|
||||
BigEndianGuard.new.run_if(:big_endian, &block)
|
||||
end
|
||||
|
||||
def little_endian(&block)
|
||||
BigEndianGuard.new.run_unless(:little_endian, &block)
|
||||
end
|
||||
end
|
43
spec/mspec/lib/mspec/guards/feature.rb
Normal file
43
spec/mspec/lib/mspec/guards/feature.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
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, &block)
|
||||
FeatureGuard.new(*features).run_if(:with_feature, &block)
|
||||
end
|
||||
end
|
118
spec/mspec/lib/mspec/guards/guard.rb
Normal file
118
spec/mspec/lib/mspec/guards/guard.rb
Normal file
|
@ -0,0 +1,118 @@
|
|||
require 'mspec/runner/mspec'
|
||||
require 'mspec/runner/actions/tally'
|
||||
require 'mspec/utils/ruby_name'
|
||||
|
||||
class SpecGuard
|
||||
def self.report
|
||||
@report ||= Hash.new { |h,k| h[k] = [] }
|
||||
end
|
||||
|
||||
def self.clear
|
||||
@report = nil
|
||||
end
|
||||
|
||||
def self.finish
|
||||
report.keys.sort.each do |key|
|
||||
desc = report[key]
|
||||
size = desc.size
|
||||
spec = size == 1 ? "spec" : "specs"
|
||||
print "\n\n#{size} #{spec} omitted by guard: #{key}:\n"
|
||||
desc.each { |description| print "\n", description; }
|
||||
end
|
||||
|
||||
print "\n\n"
|
||||
end
|
||||
|
||||
def self.guards
|
||||
@guards ||= []
|
||||
end
|
||||
|
||||
def self.clear_guards
|
||||
@guards = []
|
||||
end
|
||||
|
||||
# Returns a partial Ruby version string based on +which+.
|
||||
# For example, if RUBY_VERSION = 8.2.3:
|
||||
#
|
||||
# :major => "8"
|
||||
# :minor => "8.2"
|
||||
# :tiny => "8.2.3"
|
||||
# :teeny => "8.2.3"
|
||||
# :full => "8.2.3"
|
||||
def self.ruby_version(which = :minor)
|
||||
case which
|
||||
when :major
|
||||
n = 1
|
||||
when :minor
|
||||
n = 2
|
||||
when :tiny, :teeny, :full
|
||||
n = 3
|
||||
end
|
||||
|
||||
RUBY_VERSION.split('.')[0,n].join('.')
|
||||
end
|
||||
|
||||
attr_accessor :name
|
||||
|
||||
def initialize(*args)
|
||||
@parameters = args
|
||||
end
|
||||
|
||||
def yield?(invert = false)
|
||||
return true if MSpec.mode? :unguarded
|
||||
|
||||
allow = match? ^ invert
|
||||
|
||||
if !allow and reporting?
|
||||
MSpec.guard
|
||||
MSpec.register :finish, SpecGuard
|
||||
MSpec.register :add, self
|
||||
return true
|
||||
elsif MSpec.mode? :verify
|
||||
return true
|
||||
end
|
||||
|
||||
allow
|
||||
end
|
||||
|
||||
def run_if(name, &block)
|
||||
@name = name
|
||||
yield if yield?(false)
|
||||
ensure
|
||||
unregister
|
||||
end
|
||||
|
||||
def run_unless(name, &block)
|
||||
@name = name
|
||||
yield if yield?(true)
|
||||
ensure
|
||||
unregister
|
||||
end
|
||||
|
||||
def reporting?
|
||||
MSpec.mode?(:report) or
|
||||
(MSpec.mode?(:report_on) and SpecGuard.guards.include?(name))
|
||||
end
|
||||
|
||||
def report_key
|
||||
"#{name} #{@parameters.join(", ")}"
|
||||
end
|
||||
|
||||
def record(description)
|
||||
SpecGuard.report[report_key] << description
|
||||
end
|
||||
|
||||
def add(example)
|
||||
record example.description
|
||||
MSpec.retrieve(:formatter).tally.counter.guards!
|
||||
end
|
||||
|
||||
def unregister
|
||||
MSpec.unguard
|
||||
MSpec.unregister :add, self
|
||||
end
|
||||
|
||||
def match?
|
||||
raise "must be implemented by the subclass"
|
||||
end
|
||||
end
|
78
spec/mspec/lib/mspec/guards/platform.rb
Normal file
78
spec/mspec/lib/mspec/guards/platform.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
require 'mspec/guards/guard'
|
||||
|
||||
class PlatformGuard < SpecGuard
|
||||
def self.implementation?(*args)
|
||||
args.any? do |name|
|
||||
case name
|
||||
when :rubinius
|
||||
RUBY_NAME.start_with?('rbx')
|
||||
when :ruby, :jruby, :truffleruby, :ironruby, :macruby, :maglev, :topaz, :opal
|
||||
RUBY_NAME.start_with?(name.to_s)
|
||||
else
|
||||
raise "unknown implementation #{name}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.standard?
|
||||
implementation? :ruby
|
||||
end
|
||||
|
||||
HOST_OS = begin
|
||||
require 'rbconfig'
|
||||
RbConfig::CONFIG['host_os'] || RUBY_PLATFORM
|
||||
rescue LoadError
|
||||
RUBY_PLATFORM
|
||||
end.downcase
|
||||
|
||||
def self.os?(*oses)
|
||||
oses.any? do |os|
|
||||
raise ":java is not a valid OS" if os == :java
|
||||
if os == :windows
|
||||
HOST_OS =~ /(mswin|mingw)/
|
||||
else
|
||||
HOST_OS.include?(os.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.windows?
|
||||
os?(:windows)
|
||||
end
|
||||
|
||||
def self.wordsize?(size)
|
||||
size == 8 * 1.size
|
||||
end
|
||||
|
||||
def initialize(*args)
|
||||
if args.last.is_a?(Hash)
|
||||
@options, @platforms = args.last, args[0..-2]
|
||||
else
|
||||
@options, @platforms = {}, args
|
||||
end
|
||||
@parameters = args
|
||||
end
|
||||
|
||||
def match?
|
||||
match = @platforms.empty? ? true : PlatformGuard.os?(*@platforms)
|
||||
@options.each do |key, value|
|
||||
case key
|
||||
when :os
|
||||
match &&= PlatformGuard.os?(*value)
|
||||
when :wordsize
|
||||
match &&= PlatformGuard.wordsize? value
|
||||
end
|
||||
end
|
||||
match
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def platform_is(*args, &block)
|
||||
PlatformGuard.new(*args).run_if(:platform_is, &block)
|
||||
end
|
||||
|
||||
def platform_is_not(*args, &block)
|
||||
PlatformGuard.new(*args).run_unless(:platform_is_not, &block)
|
||||
end
|
||||
end
|
13
spec/mspec/lib/mspec/guards/quarantine.rb
Normal file
13
spec/mspec/lib/mspec/guards/quarantine.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
require 'mspec/guards/guard'
|
||||
|
||||
class QuarantineGuard < SpecGuard
|
||||
def match?
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def quarantine!(&block)
|
||||
QuarantineGuard.new.run_unless(:quarantine!, &block)
|
||||
end
|
||||
end
|
17
spec/mspec/lib/mspec/guards/superuser.rb
Normal file
17
spec/mspec/lib/mspec/guards/superuser.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
require 'mspec/guards/guard'
|
||||
|
||||
class SuperUserGuard < SpecGuard
|
||||
def match?
|
||||
Process.euid == 0
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def as_superuser(&block)
|
||||
SuperUserGuard.new.run_if(:as_superuser, &block)
|
||||
end
|
||||
|
||||
def as_user(&block)
|
||||
SuperUserGuard.new.run_unless(:as_user, &block)
|
||||
end
|
||||
end
|
16
spec/mspec/lib/mspec/guards/support.rb
Normal file
16
spec/mspec/lib/mspec/guards/support.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
require 'mspec/guards/platform'
|
||||
|
||||
class SupportedGuard < SpecGuard
|
||||
def match?
|
||||
if @parameters.include? :ruby
|
||||
raise Exception, "improper use of not_supported_on guard"
|
||||
end
|
||||
!PlatformGuard.standard? and PlatformGuard.implementation?(*@parameters)
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def not_supported_on(*args, &block)
|
||||
SupportedGuard.new(*args).run_unless(:not_supported_on, &block)
|
||||
end
|
||||
end
|
39
spec/mspec/lib/mspec/guards/version.rb
Normal file
39
spec/mspec/lib/mspec/guards/version.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
require 'mspec/utils/deprecate'
|
||||
require 'mspec/utils/version'
|
||||
require 'mspec/guards/guard'
|
||||
|
||||
class VersionGuard < SpecGuard
|
||||
FULL_RUBY_VERSION = SpecVersion.new SpecGuard.ruby_version(:full)
|
||||
|
||||
def initialize(version)
|
||||
case version
|
||||
when String
|
||||
@version = SpecVersion.new version
|
||||
when Range
|
||||
MSpec.deprecate "an empty version range end", 'a specific version' if version.end.empty?
|
||||
a = SpecVersion.new version.begin
|
||||
b = SpecVersion.new version.end
|
||||
unless version.exclude_end?
|
||||
MSpec.deprecate "ruby_version_is with an inclusive range", 'an exclusive range ("2.1"..."2.3")'
|
||||
end
|
||||
@version = version.exclude_end? ? a...b : a..b
|
||||
else
|
||||
raise "version must be a String or Range but was a #{version.class}"
|
||||
end
|
||||
@parameters = [version]
|
||||
end
|
||||
|
||||
def match?
|
||||
if Range === @version
|
||||
@version.include? FULL_RUBY_VERSION
|
||||
else
|
||||
FULL_RUBY_VERSION >= @version
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def ruby_version_is(*args, &block)
|
||||
VersionGuard.new(*args).run_if(:ruby_version_is, &block)
|
||||
end
|
||||
end
|
12
spec/mspec/lib/mspec/helpers.rb
Normal file
12
spec/mspec/lib/mspec/helpers.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
require 'mspec/helpers/argf'
|
||||
require 'mspec/helpers/argv'
|
||||
require 'mspec/helpers/datetime'
|
||||
require 'mspec/helpers/fixture'
|
||||
require 'mspec/helpers/flunk'
|
||||
require 'mspec/helpers/fs'
|
||||
require 'mspec/helpers/io'
|
||||
require 'mspec/helpers/mock_to_path'
|
||||
require 'mspec/helpers/numeric'
|
||||
require 'mspec/helpers/ruby_exe'
|
||||
require 'mspec/helpers/scratch'
|
||||
require 'mspec/helpers/tmp'
|
37
spec/mspec/lib/mspec/helpers/argf.rb
Normal file
37
spec/mspec/lib/mspec/helpers/argf.rb
Normal file
|
@ -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
|
46
spec/mspec/lib/mspec/helpers/argv.rb
Normal file
46
spec/mspec/lib/mspec/helpers/argv.rb
Normal file
|
@ -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
|
51
spec/mspec/lib/mspec/helpers/datetime.rb
Normal file
51
spec/mspec/lib/mspec/helpers/datetime.rb
Normal file
|
@ -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
|
26
spec/mspec/lib/mspec/helpers/fixture.rb
Normal file
26
spec/mspec/lib/mspec/helpers/fixture.rb
Normal file
|
@ -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
|
5
spec/mspec/lib/mspec/helpers/flunk.rb
Normal file
5
spec/mspec/lib/mspec/helpers/flunk.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class Object
|
||||
def flunk(msg="This example is a failure")
|
||||
SpecExpectation.fail_with "Failed:", msg
|
||||
end
|
||||
end
|
62
spec/mspec/lib/mspec/helpers/fs.rb
Normal file
62
spec/mspec/lib/mspec/helpers/fs.rb
Normal file
|
@ -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
|
113
spec/mspec/lib/mspec/helpers/io.rb
Normal file
113
spec/mspec/lib/mspec/helpers/io.rb
Normal file
|
@ -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
|
8
spec/mspec/lib/mspec/helpers/mock_to_path.rb
Normal file
8
spec/mspec/lib/mspec/helpers/mock_to_path.rb
Normal file
|
@ -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
|
72
spec/mspec/lib/mspec/helpers/numeric.rb
Normal file
72
spec/mspec/lib/mspec/helpers/numeric.rb
Normal file
|
@ -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
|
178
spec/mspec/lib/mspec/helpers/ruby_exe.rb
Normal file
178
spec/mspec/lib/mspec/helpers/ruby_exe.rb
Normal file
|
@ -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
|
17
spec/mspec/lib/mspec/helpers/scratch.rb
Normal file
17
spec/mspec/lib/mspec/helpers/scratch.rb
Normal file
|
@ -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
|
45
spec/mspec/lib/mspec/helpers/tmp.rb
Normal file
45
spec/mspec/lib/mspec/helpers/tmp.rb
Normal file
|
@ -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
|
35
spec/mspec/lib/mspec/matchers.rb
Normal file
35
spec/mspec/lib/mspec/matchers.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
require 'mspec/matchers/base'
|
||||
require 'mspec/matchers/be_an_instance_of'
|
||||
require 'mspec/matchers/be_ancestor_of'
|
||||
require 'mspec/matchers/be_close'
|
||||
require 'mspec/matchers/be_computed_by'
|
||||
require 'mspec/matchers/be_empty'
|
||||
require 'mspec/matchers/be_false'
|
||||
require 'mspec/matchers/be_kind_of'
|
||||
require 'mspec/matchers/be_nan'
|
||||
require 'mspec/matchers/be_nil'
|
||||
require 'mspec/matchers/be_true'
|
||||
require 'mspec/matchers/be_true_or_false'
|
||||
require 'mspec/matchers/complain'
|
||||
require 'mspec/matchers/eql'
|
||||
require 'mspec/matchers/equal'
|
||||
require 'mspec/matchers/equal_element'
|
||||
require 'mspec/matchers/have_constant'
|
||||
require 'mspec/matchers/have_class_variable'
|
||||
require 'mspec/matchers/have_instance_method'
|
||||
require 'mspec/matchers/have_instance_variable'
|
||||
require 'mspec/matchers/have_method'
|
||||
require 'mspec/matchers/have_private_instance_method'
|
||||
require 'mspec/matchers/have_private_method'
|
||||
require 'mspec/matchers/have_protected_instance_method'
|
||||
require 'mspec/matchers/have_public_instance_method'
|
||||
require 'mspec/matchers/have_singleton_method'
|
||||
require 'mspec/matchers/include'
|
||||
require 'mspec/matchers/infinity'
|
||||
require 'mspec/matchers/match_yaml'
|
||||
require 'mspec/matchers/raise_error'
|
||||
require 'mspec/matchers/output'
|
||||
require 'mspec/matchers/output_to_fd'
|
||||
require 'mspec/matchers/respond_to'
|
||||
require 'mspec/matchers/signed_zero'
|
||||
require 'mspec/matchers/block_caller'
|
95
spec/mspec/lib/mspec/matchers/base.rb
Normal file
95
spec/mspec/lib/mspec/matchers/base.rb
Normal file
|
@ -0,0 +1,95 @@
|
|||
class SpecPositiveOperatorMatcher
|
||||
def initialize(actual)
|
||||
@actual = actual
|
||||
end
|
||||
|
||||
def ==(expected)
|
||||
unless @actual == expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"to equal #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def <(expected)
|
||||
unless @actual < expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"to be less than #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def <=(expected)
|
||||
unless @actual <= expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"to be less than or equal to #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def >(expected)
|
||||
unless @actual > expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"to be greater than #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def >=(expected)
|
||||
unless @actual >= expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"to be greater than or equal to #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def =~(expected)
|
||||
unless @actual =~ expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"to match #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class SpecNegativeOperatorMatcher
|
||||
def initialize(actual)
|
||||
@actual = actual
|
||||
end
|
||||
|
||||
def ==(expected)
|
||||
if @actual == expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"not to equal #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def <(expected)
|
||||
if @actual < expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"not to be less than #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def <=(expected)
|
||||
if @actual <= expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"not to be less than or equal to #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def >(expected)
|
||||
if @actual > expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"not to be greater than #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def >=(expected)
|
||||
if @actual >= expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"not to be greater than or equal to #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
|
||||
def =~(expected)
|
||||
if @actual =~ expected
|
||||
SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}",
|
||||
"not to match #{expected.pretty_inspect}")
|
||||
end
|
||||
end
|
||||
end
|
26
spec/mspec/lib/mspec/matchers/be_an_instance_of.rb
Normal file
26
spec/mspec/lib/mspec/matchers/be_an_instance_of.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_an_instance_of(expected)
|
||||
BeAnInstanceOfMatcher.new(expected)
|
||||
end
|
||||
end
|
24
spec/mspec/lib/mspec/matchers/be_ancestor_of.rb
Normal file
24
spec/mspec/lib/mspec/matchers/be_ancestor_of.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_ancestor_of(expected)
|
||||
BeAncestorOfMatcher.new(expected)
|
||||
end
|
||||
end
|
27
spec/mspec/lib/mspec/matchers/be_close.rb
Normal file
27
spec/mspec/lib/mspec/matchers/be_close.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
TOLERANCE = 0.00003 unless Object.const_defined?(: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 #{@expected}", "to be within +/- #{@tolerance} of #{@actual}"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@expected}", "not to be within +/- #{@tolerance} of #{@actual}"]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def be_close(expected, tolerance)
|
||||
BeCloseMatcher.new(expected, tolerance)
|
||||
end
|
||||
end
|
37
spec/mspec/lib/mspec/matchers/be_computed_by.rb
Normal file
37
spec/mspec/lib/mspec/matchers/be_computed_by.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_computed_by(sym, *args)
|
||||
BeComputedByMatcher.new(sym, *args)
|
||||
end
|
||||
end
|
20
spec/mspec/lib/mspec/matchers/be_empty.rb
Normal file
20
spec/mspec/lib/mspec/matchers/be_empty.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_empty
|
||||
BeEmptyMatcher.new
|
||||
end
|
||||
end
|
20
spec/mspec/lib/mspec/matchers/be_false.rb
Normal file
20
spec/mspec/lib/mspec/matchers/be_false.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_false
|
||||
BeFalseMatcher.new
|
||||
end
|
||||
end
|
24
spec/mspec/lib/mspec/matchers/be_kind_of.rb
Normal file
24
spec/mspec/lib/mspec/matchers/be_kind_of.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_kind_of(expected)
|
||||
BeKindOfMatcher.new(expected)
|
||||
end
|
||||
end
|
20
spec/mspec/lib/mspec/matchers/be_nan.rb
Normal file
20
spec/mspec/lib/mspec/matchers/be_nan.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_nan
|
||||
BeNaNMatcher.new
|
||||
end
|
||||
end
|
20
spec/mspec/lib/mspec/matchers/be_nil.rb
Normal file
20
spec/mspec/lib/mspec/matchers/be_nil.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_nil
|
||||
BeNilMatcher.new
|
||||
end
|
||||
end
|
20
spec/mspec/lib/mspec/matchers/be_true.rb
Normal file
20
spec/mspec/lib/mspec/matchers/be_true.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_true
|
||||
BeTrueMatcher.new
|
||||
end
|
||||
end
|
20
spec/mspec/lib/mspec/matchers/be_true_or_false.rb
Normal file
20
spec/mspec/lib/mspec/matchers/be_true_or_false.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_true_or_false
|
||||
BeTrueOrFalseMatcher.new
|
||||
end
|
||||
end
|
35
spec/mspec/lib/mspec/matchers/block_caller.rb
Normal file
35
spec/mspec/lib/mspec/matchers/block_caller.rb
Normal file
|
@ -0,0 +1,35 @@
|
|||
class BlockingMatcher
|
||||
def matches?(block)
|
||||
started = false
|
||||
blocking = true
|
||||
|
||||
thread = Thread.new do
|
||||
started = true
|
||||
block.call
|
||||
|
||||
blocking = false
|
||||
end
|
||||
|
||||
while !started and status = thread.status and status != "sleep"
|
||||
Thread.pass
|
||||
end
|
||||
thread.kill
|
||||
thread.join
|
||||
|
||||
blocking
|
||||
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
|
||||
|
||||
class Object
|
||||
def block_caller(timeout = 0.1)
|
||||
BlockingMatcher.new
|
||||
end
|
||||
end
|
56
spec/mspec/lib/mspec/matchers/complain.rb
Normal file
56
spec/mspec/lib/mspec/matchers/complain.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
require 'mspec/helpers/io'
|
||||
|
||||
class ComplainMatcher
|
||||
def initialize(complaint)
|
||||
@complaint = complaint
|
||||
end
|
||||
|
||||
def matches?(proc)
|
||||
@saved_err = $stderr
|
||||
@stderr = $stderr = IOStub.new
|
||||
@verbose = $VERBOSE
|
||||
$VERBOSE = false
|
||||
|
||||
proc.call
|
||||
|
||||
unless @complaint.nil?
|
||||
case @complaint
|
||||
when Regexp
|
||||
return false unless $stderr =~ @complaint
|
||||
else
|
||||
return false unless $stderr == @complaint
|
||||
end
|
||||
end
|
||||
|
||||
return $stderr.empty? ? false : true
|
||||
ensure
|
||||
$VERBOSE = @verbose
|
||||
$stderr = @saved_err
|
||||
end
|
||||
|
||||
def failure_message
|
||||
if @complaint.nil?
|
||||
["Expected a warning", "but received none"]
|
||||
elsif @complaint.kind_of? Regexp
|
||||
["Expected warning to match: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"]
|
||||
else
|
||||
["Expected warning: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"]
|
||||
end
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
if @complaint.nil?
|
||||
["Unexpected warning: ", @stderr.chomp.inspect]
|
||||
elsif @complaint.kind_of? Regexp
|
||||
["Expected warning not to match: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"]
|
||||
else
|
||||
["Expected warning: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def complain(complaint=nil)
|
||||
ComplainMatcher.new(complaint)
|
||||
end
|
||||
end
|
26
spec/mspec/lib/mspec/matchers/eql.rb
Normal file
26
spec/mspec/lib/mspec/matchers/eql.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
class EqlMatcher
|
||||
def initialize(expected)
|
||||
@expected = expected
|
||||
end
|
||||
|
||||
def matches?(actual)
|
||||
@actual = actual
|
||||
@actual.eql?(@expected)
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@actual.pretty_inspect}",
|
||||
"to have same value and type as #{@expected.pretty_inspect}"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@actual.pretty_inspect}",
|
||||
"not to have same value or type as #{@expected.pretty_inspect}"]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def eql(expected)
|
||||
EqlMatcher.new(expected)
|
||||
end
|
||||
end
|
26
spec/mspec/lib/mspec/matchers/equal.rb
Normal file
26
spec/mspec/lib/mspec/matchers/equal.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
class EqualMatcher
|
||||
def initialize(expected)
|
||||
@expected = expected
|
||||
end
|
||||
|
||||
def matches?(actual)
|
||||
@actual = actual
|
||||
@actual.equal?(@expected)
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@actual.pretty_inspect}",
|
||||
"to be identical to #{@expected.pretty_inspect}"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@actual.pretty_inspect}",
|
||||
"not to be identical to #{@expected.pretty_inspect}"]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def equal(expected)
|
||||
EqualMatcher.new(expected)
|
||||
end
|
||||
end
|
78
spec/mspec/lib/mspec/matchers/equal_element.rb
Normal file
78
spec/mspec/lib/mspec/matchers/equal_element.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
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("</" + @element + ">")}$/
|
||||
matched &&= actual =~ /#{Regexp.quote(">" + @content + "</")}/ if @content
|
||||
end
|
||||
|
||||
if @attributes
|
||||
if @attributes.empty?
|
||||
matched &&= actual.scan(/\w+\=\"(.*)\"/).size == 0
|
||||
else
|
||||
@attributes.each do |key, value|
|
||||
if value == true
|
||||
matched &&= (actual.scan(/#{Regexp.quote(key)}(\s|>)/).size == 1)
|
||||
else
|
||||
matched &&= (actual.scan(%Q{ #{key}="#{value}"}).size == 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
!!matched
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@actual.pretty_inspect}",
|
||||
"to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@actual.pretty_inspect}",
|
||||
"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
|
||||
|
||||
class Object
|
||||
def equal_element(*args)
|
||||
EqualElementMatcher.new(*args)
|
||||
end
|
||||
end
|
12
spec/mspec/lib/mspec/matchers/have_class_variable.rb
Normal file
12
spec/mspec/lib/mspec/matchers/have_class_variable.rb
Normal file
|
@ -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
|
12
spec/mspec/lib/mspec/matchers/have_constant.rb
Normal file
12
spec/mspec/lib/mspec/matchers/have_constant.rb
Normal file
|
@ -0,0 +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
|
24
spec/mspec/lib/mspec/matchers/have_instance_method.rb
Normal file
24
spec/mspec/lib/mspec/matchers/have_instance_method.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
require 'mspec/matchers/method'
|
||||
|
||||
class HaveInstanceMethodMatcher < MethodMatcher
|
||||
def matches?(mod)
|
||||
@mod = mod
|
||||
mod.instance_methods(@include_super).include? @method
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@mod} to have instance method '#{@method.to_s}'",
|
||||
"but it does not"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@mod} NOT to have instance method '#{@method.to_s}'",
|
||||
"but it does"]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def have_instance_method(method, include_super=true)
|
||||
HaveInstanceMethodMatcher.new method, include_super
|
||||
end
|
||||
end
|
12
spec/mspec/lib/mspec/matchers/have_instance_variable.rb
Normal file
12
spec/mspec/lib/mspec/matchers/have_instance_variable.rb
Normal file
|
@ -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
|
24
spec/mspec/lib/mspec/matchers/have_method.rb
Normal file
24
spec/mspec/lib/mspec/matchers/have_method.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
require 'mspec/matchers/method'
|
||||
|
||||
class HaveMethodMatcher < MethodMatcher
|
||||
def matches?(mod)
|
||||
@mod = mod
|
||||
@mod.methods(@include_super).include? @method
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@mod} to have method '#{@method.to_s}'",
|
||||
"but it does not"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@mod} NOT to have method '#{@method.to_s}'",
|
||||
"but it does"]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def have_method(method, include_super=true)
|
||||
HaveMethodMatcher.new method, include_super
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
require 'mspec/matchers/method'
|
||||
|
||||
class HavePrivateInstanceMethodMatcher < MethodMatcher
|
||||
def matches?(mod)
|
||||
@mod = mod
|
||||
mod.private_instance_methods(@include_super).include? @method
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@mod} to have private instance method '#{@method.to_s}'",
|
||||
"but it does not"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@mod} NOT to have private instance method '#{@method.to_s}'",
|
||||
"but it does"]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def have_private_instance_method(method, include_super=true)
|
||||
HavePrivateInstanceMethodMatcher.new method, include_super
|
||||
end
|
||||
end
|
24
spec/mspec/lib/mspec/matchers/have_private_method.rb
Normal file
24
spec/mspec/lib/mspec/matchers/have_private_method.rb
Normal file
|
@ -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
|
24
spec/mspec/lib/mspec/matchers/have_public_instance_method.rb
Normal file
24
spec/mspec/lib/mspec/matchers/have_public_instance_method.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
require 'mspec/matchers/method'
|
||||
|
||||
class HavePublicInstanceMethodMatcher < MethodMatcher
|
||||
def matches?(mod)
|
||||
@mod = mod
|
||||
mod.public_instance_methods(@include_super).include? @method
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@mod} to have public instance method '#{@method.to_s}'",
|
||||
"but it does not"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@mod} NOT to have public instance method '#{@method.to_s}'",
|
||||
"but it does"]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def have_public_instance_method(method, include_super=true)
|
||||
HavePublicInstanceMethodMatcher.new method, include_super
|
||||
end
|
||||
end
|
24
spec/mspec/lib/mspec/matchers/have_singleton_method.rb
Normal file
24
spec/mspec/lib/mspec/matchers/have_singleton_method.rb
Normal file
|
@ -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
|
32
spec/mspec/lib/mspec/matchers/include.rb
Normal file
32
spec/mspec/lib/mspec/matchers/include.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
class IncludeMatcher
|
||||
def initialize(*expected)
|
||||
@expected = expected
|
||||
end
|
||||
|
||||
def matches?(actual)
|
||||
@actual = actual
|
||||
@expected.each do |e|
|
||||
@element = e
|
||||
unless @actual.include?(e)
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@actual.inspect}", "to include #{@element.inspect}"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@actual.inspect}", "not to include #{@element.inspect}"]
|
||||
end
|
||||
end
|
||||
|
||||
# Cannot override #include at the toplevel in MRI
|
||||
module MSpec
|
||||
def include(*expected)
|
||||
IncludeMatcher.new(*expected)
|
||||
end
|
||||
module_function :include
|
||||
end
|
28
spec/mspec/lib/mspec/matchers/infinity.rb
Normal file
28
spec/mspec/lib/mspec/matchers/infinity.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
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
|
||||
|
||||
class Object
|
||||
def be_positive_infinity
|
||||
InfinityMatcher.new(1)
|
||||
end
|
||||
|
||||
def be_negative_infinity
|
||||
InfinityMatcher.new(-1)
|
||||
end
|
||||
end
|
47
spec/mspec/lib/mspec/matchers/match_yaml.rb
Normal file
47
spec/mspec/lib/mspec/matchers/match_yaml.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
class MatchYAMLMatcher
|
||||
|
||||
def initialize(expected)
|
||||
if valid_yaml?(expected)
|
||||
@expected = expected
|
||||
else
|
||||
@expected = expected.to_yaml
|
||||
end
|
||||
end
|
||||
|
||||
def matches?(actual)
|
||||
@actual = actual
|
||||
clean_yaml(@actual) == clean_yaml(@expected)
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@actual.inspect}", " to match #{@expected.inspect}"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@actual.inspect}", " to match #{@expected.inspect}"]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def clean_yaml(yaml)
|
||||
yaml.gsub(/([^-]|^---)\s+\n/, "\\1\n").sub(/\n\.\.\.\n$/, "\n")
|
||||
end
|
||||
|
||||
def valid_yaml?(obj)
|
||||
require 'yaml'
|
||||
begin
|
||||
YAML.load(obj)
|
||||
rescue
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def match_yaml(expected)
|
||||
MatchYAMLMatcher.new(expected)
|
||||
end
|
||||
end
|
||||
|
10
spec/mspec/lib/mspec/matchers/method.rb
Normal file
10
spec/mspec/lib/mspec/matchers/method.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class MethodMatcher
|
||||
def initialize(method, include_super=true)
|
||||
@include_super = include_super
|
||||
@method = method.to_sym
|
||||
end
|
||||
|
||||
def matches?(mod)
|
||||
raise Exception, "define #matches? in the subclass"
|
||||
end
|
||||
end
|
67
spec/mspec/lib/mspec/matchers/output.rb
Normal file
67
spec/mspec/lib/mspec/matchers/output.rb
Normal file
|
@ -0,0 +1,67 @@
|
|||
require 'mspec/helpers/io'
|
||||
|
||||
class OutputMatcher
|
||||
def initialize(stdout, stderr)
|
||||
@out = stdout
|
||||
@err = stderr
|
||||
end
|
||||
|
||||
def matches?(proc)
|
||||
@saved_out = $stdout
|
||||
@saved_err = $stderr
|
||||
@stdout = $stdout = IOStub.new
|
||||
@stderr = $stderr = IOStub.new
|
||||
|
||||
proc.call
|
||||
|
||||
unless @out.nil?
|
||||
case @out
|
||||
when Regexp
|
||||
return false unless $stdout =~ @out
|
||||
else
|
||||
return false unless $stdout == @out
|
||||
end
|
||||
end
|
||||
|
||||
unless @err.nil?
|
||||
case @err
|
||||
when Regexp
|
||||
return false unless $stderr =~ @err
|
||||
else
|
||||
return false unless $stderr == @err
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
ensure
|
||||
$stdout = @saved_out
|
||||
$stderr = @saved_err
|
||||
end
|
||||
|
||||
def failure_message
|
||||
expected_out = "\n"
|
||||
actual_out = "\n"
|
||||
unless @out.nil?
|
||||
expected_out += " $stdout: #{@out.inspect}\n"
|
||||
actual_out += " $stdout: #{@stdout.inspect}\n"
|
||||
end
|
||||
unless @err.nil?
|
||||
expected_out += " $stderr: #{@err.inspect}\n"
|
||||
actual_out += " $stderr: #{@stderr.inspect}\n"
|
||||
end
|
||||
["Expected:#{expected_out}", " got:#{actual_out}"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
out = ""
|
||||
out += " $stdout: #{@stdout.chomp.dump}\n" unless @out.nil?
|
||||
out += " $stderr: #{@stderr.chomp.dump}\n" unless @err.nil?
|
||||
["Expected output not to be:\n", out]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def output(stdout=nil, stderr=nil)
|
||||
OutputMatcher.new(stdout, stderr)
|
||||
end
|
||||
end
|
71
spec/mspec/lib/mspec/matchers/output_to_fd.rb
Normal file
71
spec/mspec/lib/mspec/matchers/output_to_fd.rb
Normal file
|
@ -0,0 +1,71 @@
|
|||
require 'mspec/helpers/tmp'
|
||||
|
||||
# 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 with_tmp
|
||||
path = tmp("mspec_output_to_#{$$}_#{Time.now.to_i}")
|
||||
File.open(path, 'w+') { |io|
|
||||
yield(io)
|
||||
}
|
||||
ensure
|
||||
File.delete path if path
|
||||
end
|
||||
|
||||
def matches?(block)
|
||||
old_to = @to.dup
|
||||
with_tmp do |out|
|
||||
# Replacing with a file handle so that Readline etc. work
|
||||
@to.reopen out
|
||||
begin
|
||||
block.call
|
||||
ensure
|
||||
@to.reopen old_to
|
||||
old_to.close
|
||||
end
|
||||
|
||||
out.rewind
|
||||
@actual = out.read
|
||||
|
||||
case @expected
|
||||
when Regexp
|
||||
!(@actual =~ @expected).nil?
|
||||
else
|
||||
@actual == @expected
|
||||
end
|
||||
end
|
||||
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
|
79
spec/mspec/lib/mspec/matchers/raise_error.rb
Normal file
79
spec/mspec/lib/mspec/matchers/raise_error.rb
Normal file
|
@ -0,0 +1,79 @@
|
|||
require 'mspec/utils/deprecate'
|
||||
|
||||
class RaiseErrorMatcher
|
||||
def initialize(exception, message, &block)
|
||||
@exception = exception
|
||||
@message = message
|
||||
@block = block
|
||||
end
|
||||
|
||||
def matches?(proc)
|
||||
@result = proc.call
|
||||
return false
|
||||
rescue Exception => @actual
|
||||
if matching_exception?(@actual)
|
||||
return true
|
||||
else
|
||||
raise @actual
|
||||
end
|
||||
end
|
||||
|
||||
def matching_exception?(exc)
|
||||
return false unless @exception === exc
|
||||
if @message then
|
||||
case @message
|
||||
when String
|
||||
return false if @message != exc.message
|
||||
when Regexp
|
||||
return false if @message !~ exc.message
|
||||
end
|
||||
end
|
||||
|
||||
# The block has its own expectations and will throw an exception if it fails
|
||||
@block[exc] if @block
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
def exception_class_and_message(exception_class, message)
|
||||
if message
|
||||
"#{exception_class} (#{message})"
|
||||
else
|
||||
"#{exception_class}"
|
||||
end
|
||||
end
|
||||
|
||||
def format_expected_exception
|
||||
exception_class_and_message(@exception, @message)
|
||||
end
|
||||
|
||||
def format_exception(exception)
|
||||
exception_class_and_message(exception.class, exception.message)
|
||||
end
|
||||
|
||||
def failure_message
|
||||
message = ["Expected #{format_expected_exception}"]
|
||||
|
||||
if @actual then
|
||||
message << "but got #{format_exception(@actual)}"
|
||||
else
|
||||
message << "but no exception was raised (#{@result.pretty_inspect.chomp} was returned)"
|
||||
end
|
||||
|
||||
message
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
message = ["Expected to not get #{format_expected_exception}", ""]
|
||||
unless @actual.class == @exception
|
||||
message[1] = "but got #{format_exception(@actual)}"
|
||||
end
|
||||
message
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def raise_error(exception=Exception, message=nil, &block)
|
||||
RaiseErrorMatcher.new(exception, message, &block)
|
||||
end
|
||||
end
|
24
spec/mspec/lib/mspec/matchers/respond_to.rb
Normal file
24
spec/mspec/lib/mspec/matchers/respond_to.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class RespondToMatcher
|
||||
def initialize(expected)
|
||||
@expected = expected
|
||||
end
|
||||
|
||||
def matches?(actual)
|
||||
@actual = actual
|
||||
@actual.respond_to?(@expected)
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@actual.inspect} (#{@actual.class})", "to respond to #{@expected}"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@actual.inspect} (#{@actual.class})", "not to respond to #{@expected}"]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def respond_to(expected)
|
||||
RespondToMatcher.new(expected)
|
||||
end
|
||||
end
|
28
spec/mspec/lib/mspec/matchers/signed_zero.rb
Normal file
28
spec/mspec/lib/mspec/matchers/signed_zero.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
class SignedZeroMatcher
|
||||
def initialize(expected_sign)
|
||||
@expected_sign = expected_sign
|
||||
end
|
||||
|
||||
def matches?(actual)
|
||||
@actual = actual
|
||||
(1.0/actual).infinite? == @expected_sign
|
||||
end
|
||||
|
||||
def failure_message
|
||||
["Expected #{@actual}", "to be #{"-" if @expected_sign == -1}0.0"]
|
||||
end
|
||||
|
||||
def negative_failure_message
|
||||
["Expected #{@actual}", "not to be #{"-" if @expected_sign == -1}0.0"]
|
||||
end
|
||||
end
|
||||
|
||||
class Object
|
||||
def be_positive_zero
|
||||
SignedZeroMatcher.new(1)
|
||||
end
|
||||
|
||||
def be_negative_zero
|
||||
SignedZeroMatcher.new(-1)
|
||||
end
|
||||
end
|
24
spec/mspec/lib/mspec/matchers/variable.rb
Normal file
24
spec/mspec/lib/mspec/matchers/variable.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
class VariableMatcher
|
||||
class << self
|
||||
attr_accessor :variables_method, :description
|
||||
end
|
||||
|
||||
def initialize(variable)
|
||||
@variable = variable.to_sym
|
||||
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
|
3
spec/mspec/lib/mspec/mocks.rb
Normal file
3
spec/mspec/lib/mspec/mocks.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
require 'mspec/mocks/mock'
|
||||
require 'mspec/mocks/proxy'
|
||||
require 'mspec/mocks/object'
|
197
spec/mspec/lib/mspec/mocks/mock.rb
Normal file
197
spec/mspec/lib/mspec/mocks/mock.rb
Normal file
|
@ -0,0 +1,197 @@
|
|||
require 'mspec/expectations/expectations'
|
||||
|
||||
class Object
|
||||
alias_method :__mspec_object_id__, :object_id
|
||||
end
|
||||
|
||||
module Mock
|
||||
def self.reset
|
||||
@mocks = @stubs = @objects = nil
|
||||
end
|
||||
|
||||
def self.objects
|
||||
@objects ||= {}
|
||||
end
|
||||
|
||||
def self.mocks
|
||||
@mocks ||= Hash.new { |h,k| h[k] = [] }
|
||||
end
|
||||
|
||||
def self.stubs
|
||||
@stubs ||= Hash.new { |h,k| h[k] = [] }
|
||||
end
|
||||
|
||||
def self.replaced_name(obj, sym)
|
||||
:"__mspec_#{obj.__mspec_object_id__}_#{sym}__"
|
||||
end
|
||||
|
||||
def self.replaced_key(obj, sym)
|
||||
[replaced_name(obj, sym), sym]
|
||||
end
|
||||
|
||||
def self.has_key?(keys, sym)
|
||||
!!keys.find { |k| k.first == sym }
|
||||
end
|
||||
|
||||
def self.replaced?(sym)
|
||||
has_key?(mocks.keys, sym) or has_key?(stubs.keys, sym)
|
||||
end
|
||||
|
||||
def self.clear_replaced(key)
|
||||
mocks.delete key
|
||||
stubs.delete key
|
||||
end
|
||||
|
||||
def self.mock_respond_to?(obj, sym, include_private = false)
|
||||
name = replaced_name(obj, :respond_to?)
|
||||
if replaced? name
|
||||
obj.__send__ name, sym, include_private
|
||||
else
|
||||
obj.respond_to? sym, include_private
|
||||
end
|
||||
end
|
||||
|
||||
def self.install_method(obj, sym, type=nil)
|
||||
meta = obj.singleton_class
|
||||
|
||||
key = replaced_key obj, sym
|
||||
sym = sym.to_sym
|
||||
|
||||
if (sym == :respond_to? or mock_respond_to?(obj, sym, true)) and !replaced?(key.first)
|
||||
meta.__send__ :alias_method, key.first, sym
|
||||
end
|
||||
|
||||
meta.class_eval {
|
||||
define_method(sym) do |*args, &block|
|
||||
Mock.verify_call self, sym, *args, &block
|
||||
end
|
||||
}
|
||||
|
||||
proxy = MockProxy.new type
|
||||
|
||||
if proxy.mock?
|
||||
MSpec.expectation
|
||||
MSpec.actions :expectation, MSpec.current.state
|
||||
end
|
||||
|
||||
if proxy.stub?
|
||||
stubs[key].unshift proxy
|
||||
else
|
||||
mocks[key] << proxy
|
||||
end
|
||||
objects[key] = obj
|
||||
|
||||
proxy
|
||||
end
|
||||
|
||||
def self.name_or_inspect(obj)
|
||||
obj.instance_variable_get(:@name) || obj.inspect
|
||||
end
|
||||
|
||||
def self.verify_count
|
||||
mocks.each do |key, proxies|
|
||||
obj = objects[key]
|
||||
proxies.each do |proxy|
|
||||
qualifier, count = proxy.count
|
||||
pass = case qualifier
|
||||
when :at_least
|
||||
proxy.calls >= count
|
||||
when :at_most
|
||||
proxy.calls <= count
|
||||
when :exactly
|
||||
proxy.calls == count
|
||||
when :any_number_of_times
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
unless pass
|
||||
SpecExpectation.fail_with(
|
||||
"Mock '#{name_or_inspect obj}' expected to receive '#{key.last}' " + \
|
||||
"#{qualifier.to_s.sub('_', ' ')} #{count} times",
|
||||
"but received it #{proxy.calls} times")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.verify_call(obj, sym, *args, &block)
|
||||
compare = *args
|
||||
compare = compare.first if compare.length <= 1
|
||||
|
||||
key = replaced_key obj, sym
|
||||
[mocks, stubs].each do |proxies|
|
||||
proxies[key].each do |proxy|
|
||||
pass = case proxy.arguments
|
||||
when :any_args
|
||||
true
|
||||
when :no_args
|
||||
compare.nil?
|
||||
else
|
||||
proxy.arguments == compare
|
||||
end
|
||||
|
||||
if proxy.yielding?
|
||||
if block
|
||||
proxy.yielding.each do |args_to_yield|
|
||||
if block.arity == -1 || block.arity == args_to_yield.size
|
||||
block.call(*args_to_yield)
|
||||
else
|
||||
SpecExpectation.fail_with(
|
||||
"Mock '#{name_or_inspect obj}' asked to yield " + \
|
||||
"|#{proxy.yielding.join(', ')}| on #{sym}\n",
|
||||
"but a block with arity #{block.arity} was passed")
|
||||
end
|
||||
end
|
||||
else
|
||||
SpecExpectation.fail_with(
|
||||
"Mock '#{name_or_inspect obj}' asked to yield " + \
|
||||
"|[#{proxy.yielding.join('], [')}]| on #{sym}\n",
|
||||
"but no block was passed")
|
||||
end
|
||||
end
|
||||
|
||||
if pass
|
||||
proxy.called
|
||||
|
||||
if proxy.raising?
|
||||
raise proxy.raising
|
||||
else
|
||||
return proxy.returning
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if sym.to_sym == :respond_to?
|
||||
mock_respond_to? obj, compare
|
||||
else
|
||||
SpecExpectation.fail_with("Mock '#{name_or_inspect obj}': method #{sym}\n",
|
||||
"called with unexpected arguments (#{Array(compare).join(' ')})")
|
||||
end
|
||||
end
|
||||
|
||||
def self.cleanup
|
||||
objects.each do |key, obj|
|
||||
if obj.kind_of? MockIntObject
|
||||
clear_replaced key
|
||||
next
|
||||
end
|
||||
|
||||
replaced = key.first
|
||||
sym = key.last
|
||||
meta = obj.singleton_class
|
||||
|
||||
if mock_respond_to? obj, replaced, true
|
||||
meta.__send__ :alias_method, sym, replaced
|
||||
meta.__send__ :remove_method, replaced
|
||||
else
|
||||
meta.__send__ :remove_method, sym
|
||||
end
|
||||
|
||||
clear_replaced key
|
||||
end
|
||||
ensure
|
||||
reset
|
||||
end
|
||||
end
|
28
spec/mspec/lib/mspec/mocks/object.rb
Normal file
28
spec/mspec/lib/mspec/mocks/object.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
require 'mspec/mocks/proxy'
|
||||
|
||||
class Object
|
||||
def stub!(sym)
|
||||
Mock.install_method self, sym, :stub
|
||||
end
|
||||
|
||||
def should_receive(sym)
|
||||
Mock.install_method self, sym
|
||||
end
|
||||
|
||||
def should_not_receive(sym)
|
||||
proxy = Mock.install_method self, sym
|
||||
proxy.exactly(0).times
|
||||
end
|
||||
|
||||
def mock(name, options={})
|
||||
MockObject.new name, options
|
||||
end
|
||||
|
||||
def mock_int(val)
|
||||
MockIntObject.new(val)
|
||||
end
|
||||
|
||||
def mock_numeric(name, options={})
|
||||
NumericMockObject.new name, options
|
||||
end
|
||||
end
|
186
spec/mspec/lib/mspec/mocks/proxy.rb
Normal file
186
spec/mspec/lib/mspec/mocks/proxy.rb
Normal file
|
@ -0,0 +1,186 @@
|
|||
class MockObject
|
||||
def initialize(name, options={})
|
||||
@name = name
|
||||
@null = options[:null_object]
|
||||
end
|
||||
|
||||
def method_missing(sym, *args, &block)
|
||||
@null ? self : super
|
||||
end
|
||||
private :method_missing
|
||||
end
|
||||
|
||||
class NumericMockObject < Numeric
|
||||
def initialize(name, options={})
|
||||
@name = name
|
||||
@null = options[:null_object]
|
||||
end
|
||||
|
||||
def method_missing(sym, *args, &block)
|
||||
@null ? self : super
|
||||
end
|
||||
|
||||
def singleton_method_added(val)
|
||||
end
|
||||
end
|
||||
|
||||
class MockIntObject
|
||||
def initialize(val)
|
||||
@value = val
|
||||
@calls = 0
|
||||
|
||||
key = [self, :to_int]
|
||||
|
||||
Mock.objects[key] = self
|
||||
Mock.mocks[key] << self
|
||||
end
|
||||
|
||||
attr_reader :calls
|
||||
|
||||
def to_int
|
||||
@calls += 1
|
||||
@value.to_int
|
||||
end
|
||||
|
||||
def count
|
||||
[:at_least, 1]
|
||||
end
|
||||
end
|
||||
|
||||
class MockProxy
|
||||
attr_reader :raising, :yielding
|
||||
|
||||
def initialize(type=nil)
|
||||
@multiple_returns = nil
|
||||
@returning = nil
|
||||
@raising = nil
|
||||
@yielding = []
|
||||
@arguments = :any_args
|
||||
@type = type || :mock
|
||||
end
|
||||
|
||||
def mock?
|
||||
@type == :mock
|
||||
end
|
||||
|
||||
def stub?
|
||||
@type == :stub
|
||||
end
|
||||
|
||||
def count
|
||||
@count ||= mock? ? [:exactly, 1] : [:any_number_of_times, 0]
|
||||
end
|
||||
|
||||
def arguments
|
||||
@arguments
|
||||
end
|
||||
|
||||
def returning
|
||||
if @multiple_returns
|
||||
if @returning.size == 1
|
||||
@multiple_returns = false
|
||||
return @returning = @returning.shift
|
||||
end
|
||||
return @returning.shift
|
||||
end
|
||||
@returning
|
||||
end
|
||||
|
||||
def times
|
||||
self
|
||||
end
|
||||
|
||||
def calls
|
||||
@calls ||= 0
|
||||
end
|
||||
|
||||
def called
|
||||
@calls = calls + 1
|
||||
end
|
||||
|
||||
def exactly(n)
|
||||
@count = [:exactly, n_times(n)]
|
||||
self
|
||||
end
|
||||
|
||||
def at_least(n)
|
||||
@count = [:at_least, n_times(n)]
|
||||
self
|
||||
end
|
||||
|
||||
def at_most(n)
|
||||
@count = [:at_most, n_times(n)]
|
||||
self
|
||||
end
|
||||
|
||||
def once
|
||||
exactly 1
|
||||
end
|
||||
|
||||
def twice
|
||||
exactly 2
|
||||
end
|
||||
|
||||
def any_number_of_times
|
||||
@count = [:any_number_of_times, 0]
|
||||
self
|
||||
end
|
||||
|
||||
def with(*args)
|
||||
raise ArgumentError, "you must specify the expected arguments" if args.empty?
|
||||
if args.length == 1
|
||||
@arguments = args.first
|
||||
else
|
||||
@arguments = args
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def and_return(*args)
|
||||
case args.size
|
||||
when 0
|
||||
@returning = nil
|
||||
when 1
|
||||
@returning = args[0]
|
||||
else
|
||||
@multiple_returns = true
|
||||
@returning = args
|
||||
count[1] = args.size if count[1] < args.size
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def and_raise(exception)
|
||||
if exception.kind_of? String
|
||||
@raising = RuntimeError.new exception
|
||||
else
|
||||
@raising = exception
|
||||
end
|
||||
end
|
||||
|
||||
def raising?
|
||||
@raising != nil
|
||||
end
|
||||
|
||||
def and_yield(*args)
|
||||
@yielding << args
|
||||
self
|
||||
end
|
||||
|
||||
def yielding?
|
||||
!@yielding.empty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def n_times(n)
|
||||
case n
|
||||
when :once
|
||||
1
|
||||
when :twice
|
||||
2
|
||||
else
|
||||
Integer n
|
||||
end
|
||||
end
|
||||
end
|
12
spec/mspec/lib/mspec/runner.rb
Normal file
12
spec/mspec/lib/mspec/runner.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
require 'mspec/mocks'
|
||||
require 'mspec/runner/mspec'
|
||||
require 'mspec/runner/context'
|
||||
require 'mspec/runner/evaluate'
|
||||
require 'mspec/runner/example'
|
||||
require 'mspec/runner/exception'
|
||||
require 'mspec/runner/object'
|
||||
require 'mspec/runner/formatters'
|
||||
require 'mspec/runner/actions'
|
||||
require 'mspec/runner/filters'
|
||||
require 'mspec/runner/shared'
|
||||
require 'mspec/runner/tag'
|
6
spec/mspec/lib/mspec/runner/actions.rb
Normal file
6
spec/mspec/lib/mspec/runner/actions.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
require 'mspec/runner/actions/tally'
|
||||
require 'mspec/runner/actions/timer'
|
||||
require 'mspec/runner/actions/filter'
|
||||
require 'mspec/runner/actions/tag'
|
||||
require 'mspec/runner/actions/taglist'
|
||||
require 'mspec/runner/actions/tagpurge'
|
40
spec/mspec/lib/mspec/runner/actions/filter.rb
Normal file
40
spec/mspec/lib/mspec/runner/actions/filter.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
require 'mspec/runner/filters/match'
|
||||
|
||||
# ActionFilter is a base class for actions that are triggered by
|
||||
# specs that match the filter. The filter may be specified by
|
||||
# strings that match spec descriptions or by tags for strings
|
||||
# that match spec descriptions.
|
||||
#
|
||||
# Unlike TagFilter and RegexpFilter, ActionFilter instances do
|
||||
# not affect the specs that are run. The filter is only used to
|
||||
# trigger the action.
|
||||
|
||||
class ActionFilter
|
||||
def initialize(tags=nil, descs=nil)
|
||||
@tags = Array(tags)
|
||||
descs = Array(descs)
|
||||
@sfilter = descs.empty? ? nil : MatchFilter.new(nil, *descs)
|
||||
@tfilter = nil
|
||||
end
|
||||
|
||||
def ===(string)
|
||||
@sfilter === string or @tfilter === string
|
||||
end
|
||||
|
||||
def load
|
||||
return if @tags.empty?
|
||||
|
||||
desc = MSpec.read_tags(@tags).map { |t| t.description }
|
||||
return if desc.empty?
|
||||
|
||||
@tfilter = MatchFilter.new(nil, *desc)
|
||||
end
|
||||
|
||||
def register
|
||||
MSpec.register :load, self
|
||||
end
|
||||
|
||||
def unregister
|
||||
MSpec.unregister :load, self
|
||||
end
|
||||
end
|
301
spec/mspec/lib/mspec/runner/actions/leakchecker.rb
Normal file
301
spec/mspec/lib/mspec/runner/actions/leakchecker.rb
Normal file
|
@ -0,0 +1,301 @@
|
|||
# Adapted from ruby's test/lib/leakchecker.rb.
|
||||
# Ruby's 2-clause BSDL follows.
|
||||
|
||||
# Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
|
||||
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
# SUCH DAMAGE.
|
||||
|
||||
class LeakChecker
|
||||
def initialize
|
||||
@fd_info = find_fds
|
||||
@tempfile_info = find_tempfiles
|
||||
@thread_info = find_threads
|
||||
@env_info = find_env
|
||||
@argv_info = find_argv
|
||||
@encoding_info = find_encodings
|
||||
end
|
||||
|
||||
def check(test_name)
|
||||
@no_leaks = true
|
||||
leaks = [
|
||||
check_fd_leak(test_name),
|
||||
check_tempfile_leak(test_name),
|
||||
check_thread_leak(test_name),
|
||||
check_process_leak(test_name),
|
||||
check_env(test_name),
|
||||
check_argv(test_name),
|
||||
check_encodings(test_name)
|
||||
]
|
||||
GC.start if leaks.any?
|
||||
return leaks.none?
|
||||
end
|
||||
|
||||
private
|
||||
def find_fds
|
||||
fd_dir = "/proc/self/fd"
|
||||
if File.directory?(fd_dir)
|
||||
fds = Dir.open(fd_dir) {|d|
|
||||
a = d.grep(/\A\d+\z/, &:to_i)
|
||||
if d.respond_to? :fileno
|
||||
a -= [d.fileno]
|
||||
end
|
||||
a
|
||||
}
|
||||
fds.sort
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def check_fd_leak(test_name)
|
||||
leaked = false
|
||||
live1 = @fd_info
|
||||
if IO.respond_to?(:console) and (m = IO.method(:console)).arity.nonzero?
|
||||
m[:close]
|
||||
end
|
||||
live2 = find_fds
|
||||
fd_closed = live1 - live2
|
||||
if !fd_closed.empty?
|
||||
fd_closed.each {|fd|
|
||||
puts "Closed file descriptor: #{test_name}: #{fd}"
|
||||
}
|
||||
end
|
||||
fd_leaked = live2 - live1
|
||||
if !fd_leaked.empty?
|
||||
leaked = true
|
||||
h = {}
|
||||
ObjectSpace.each_object(IO) {|io|
|
||||
inspect = io.inspect
|
||||
begin
|
||||
autoclose = io.autoclose?
|
||||
fd = io.fileno
|
||||
rescue IOError # closed IO object
|
||||
next
|
||||
end
|
||||
(h[fd] ||= []) << [io, autoclose, inspect]
|
||||
}
|
||||
fd_leaked.each {|fd|
|
||||
str = ''
|
||||
if h[fd]
|
||||
str << ' :'
|
||||
h[fd].map {|io, autoclose, inspect|
|
||||
s = ' ' + inspect
|
||||
s << "(not-autoclose)" if !autoclose
|
||||
s
|
||||
}.sort.each {|s|
|
||||
str << s
|
||||
}
|
||||
end
|
||||
puts "Leaked file descriptor: #{test_name}: #{fd}#{str}"
|
||||
}
|
||||
#system("lsof -p #$$") if !fd_leaked.empty?
|
||||
h.each {|fd, list|
|
||||
next if list.length <= 1
|
||||
if 1 < list.count {|io, autoclose, inspect| autoclose }
|
||||
str = list.map {|io, autoclose, inspect| " #{inspect}" + (autoclose ? "(autoclose)" : "") }.sort.join
|
||||
puts "Multiple autoclose IO object for a file descriptor:#{str}"
|
||||
end
|
||||
}
|
||||
end
|
||||
@fd_info = live2
|
||||
return leaked
|
||||
end
|
||||
|
||||
def extend_tempfile_counter
|
||||
return if defined? LeakChecker::TempfileCounter
|
||||
m = Module.new {
|
||||
@count = 0
|
||||
class << self
|
||||
attr_accessor :count
|
||||
end
|
||||
|
||||
def new(data)
|
||||
LeakChecker::TempfileCounter.count += 1
|
||||
super(data)
|
||||
end
|
||||
}
|
||||
LeakChecker.const_set(:TempfileCounter, m)
|
||||
|
||||
class << Tempfile::Remover
|
||||
prepend LeakChecker::TempfileCounter
|
||||
end
|
||||
end
|
||||
|
||||
def find_tempfiles(prev_count=-1)
|
||||
return [prev_count, []] unless defined? Tempfile
|
||||
extend_tempfile_counter
|
||||
count = TempfileCounter.count
|
||||
if prev_count == count
|
||||
[prev_count, []]
|
||||
else
|
||||
tempfiles = ObjectSpace.each_object(Tempfile).find_all {|t| t.path }
|
||||
[count, tempfiles]
|
||||
end
|
||||
end
|
||||
|
||||
def check_tempfile_leak(test_name)
|
||||
return false unless defined? Tempfile
|
||||
count1, initial_tempfiles = @tempfile_info
|
||||
count2, current_tempfiles = find_tempfiles(count1)
|
||||
leaked = false
|
||||
tempfiles_leaked = current_tempfiles - initial_tempfiles
|
||||
if !tempfiles_leaked.empty?
|
||||
leaked = true
|
||||
list = tempfiles_leaked.map {|t| t.inspect }.sort
|
||||
list.each {|str|
|
||||
puts "Leaked tempfile: #{test_name}: #{str}"
|
||||
}
|
||||
tempfiles_leaked.each {|t| t.close! }
|
||||
end
|
||||
@tempfile_info = [count2, initial_tempfiles]
|
||||
return leaked
|
||||
end
|
||||
|
||||
def find_threads
|
||||
Thread.list.find_all {|t|
|
||||
t != Thread.current && t.alive?
|
||||
}
|
||||
end
|
||||
|
||||
def check_thread_leak(test_name)
|
||||
live1 = @thread_info
|
||||
live2 = find_threads
|
||||
thread_finished = live1 - live2
|
||||
leaked = false
|
||||
if !thread_finished.empty?
|
||||
list = thread_finished.map {|t| t.inspect }.sort
|
||||
list.each {|str|
|
||||
puts "Finished thread: #{test_name}: #{str}"
|
||||
}
|
||||
end
|
||||
thread_leaked = live2 - live1
|
||||
if !thread_leaked.empty?
|
||||
leaked = true
|
||||
list = thread_leaked.map {|t| t.inspect }.sort
|
||||
list.each {|str|
|
||||
puts "Leaked thread: #{test_name}: #{str}"
|
||||
}
|
||||
end
|
||||
@thread_info = live2
|
||||
return leaked
|
||||
end
|
||||
|
||||
def check_process_leak(test_name)
|
||||
subprocesses_leaked = Process.waitall
|
||||
subprocesses_leaked.each { |pid, status|
|
||||
puts "Leaked subprocess: #{pid}: #{status}"
|
||||
}
|
||||
return !subprocesses_leaked.empty?
|
||||
end
|
||||
|
||||
def find_env
|
||||
ENV.to_h
|
||||
end
|
||||
|
||||
def check_env(test_name)
|
||||
old_env = @env_info
|
||||
new_env = find_env
|
||||
return false if old_env == new_env
|
||||
(old_env.keys | new_env.keys).sort.each {|k|
|
||||
if old_env.has_key?(k)
|
||||
if new_env.has_key?(k)
|
||||
if old_env[k] != new_env[k]
|
||||
puts "Environment variable changed: #{test_name} : #{k.inspect} changed : #{old_env[k].inspect} -> #{new_env[k].inspect}"
|
||||
end
|
||||
else
|
||||
puts "Environment variable changed: #{test_name} : #{k.inspect} deleted"
|
||||
end
|
||||
else
|
||||
if new_env.has_key?(k)
|
||||
puts "Environment variable changed: #{test_name} : #{k.inspect} added"
|
||||
else
|
||||
flunk "unreachable"
|
||||
end
|
||||
end
|
||||
}
|
||||
@env_info = new_env
|
||||
return true
|
||||
end
|
||||
|
||||
def find_argv
|
||||
ARGV.map { |e| e.dup }
|
||||
end
|
||||
|
||||
def check_argv(test_name)
|
||||
old_argv = @argv_info
|
||||
new_argv = find_argv
|
||||
leaked = false
|
||||
if new_argv != old_argv
|
||||
puts "ARGV changed: #{test_name} : #{old_argv.inspect} to #{new_argv.inspect}"
|
||||
@argv_info = new_argv
|
||||
leaked = true
|
||||
end
|
||||
return leaked
|
||||
end
|
||||
|
||||
def find_encodings
|
||||
[Encoding.default_internal, Encoding.default_external]
|
||||
end
|
||||
|
||||
def check_encodings(test_name)
|
||||
old_internal, old_external = @encoding_info
|
||||
new_internal, new_external = find_encodings
|
||||
leaked = false
|
||||
if new_internal != old_internal
|
||||
leaked = true
|
||||
puts "Encoding.default_internal changed: #{test_name} : #{old_internal} to #{new_internal}"
|
||||
end
|
||||
if new_external != old_external
|
||||
leaked = true
|
||||
puts "Encoding.default_external changed: #{test_name} : #{old_external} to #{new_external}"
|
||||
end
|
||||
@encoding_info = [new_internal, new_external]
|
||||
return leaked
|
||||
end
|
||||
|
||||
def puts(*args)
|
||||
if @no_leaks
|
||||
@no_leaks = false
|
||||
print "\n"
|
||||
end
|
||||
super(*args)
|
||||
end
|
||||
end
|
||||
|
||||
class LeakCheckerAction
|
||||
def register
|
||||
MSpec.register :start, self
|
||||
MSpec.register :after, self
|
||||
end
|
||||
|
||||
def start
|
||||
@checker = LeakChecker.new
|
||||
end
|
||||
|
||||
def after(state)
|
||||
unless @checker.check(state.description)
|
||||
if state.example
|
||||
puts state.example.source_location.join(':')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
133
spec/mspec/lib/mspec/runner/actions/tag.rb
Normal file
133
spec/mspec/lib/mspec/runner/actions/tag.rb
Normal file
|
@ -0,0 +1,133 @@
|
|||
require 'mspec/runner/actions/filter'
|
||||
|
||||
# TagAction - Write tagged spec description string to a
|
||||
# tag file associated with each spec file.
|
||||
#
|
||||
# The action is triggered by specs whose descriptions
|
||||
# match the filter created with 'tags' and/or 'desc'
|
||||
#
|
||||
# The action fires in the :after event, after the spec
|
||||
# had been run. The action fires if the outcome of
|
||||
# running the spec matches 'outcome'.
|
||||
#
|
||||
# The arguments are:
|
||||
#
|
||||
# action: :add, :del
|
||||
# outcome: :pass, :fail, :all
|
||||
# tag: the tag to create/delete
|
||||
# comment: the comment to create
|
||||
# tags: zero or more tags to get matching
|
||||
# spec description strings from
|
||||
# desc: zero or more strings to match the
|
||||
# spec description strings
|
||||
|
||||
class TagAction < ActionFilter
|
||||
def initialize(action, outcome, tag, comment, tags=nil, descs=nil)
|
||||
super tags, descs
|
||||
@action = action
|
||||
@outcome = outcome
|
||||
@tag = tag
|
||||
@comment = comment
|
||||
@report = []
|
||||
@exception = false
|
||||
end
|
||||
|
||||
# Returns true if there are no _tag_ or _description_ filters. This
|
||||
# means that a TagAction matches any example by default. Otherwise,
|
||||
# returns true if either the _tag_ or the _description_ filter
|
||||
# matches +string+.
|
||||
def ===(string)
|
||||
return true unless @sfilter or @tfilter
|
||||
@sfilter === string or @tfilter === string
|
||||
end
|
||||
|
||||
# Callback for the MSpec :before event. Resets the +#exception?+
|
||||
# flag to false.
|
||||
def before(state)
|
||||
@exception = false
|
||||
end
|
||||
|
||||
# Callback for the MSpec :exception event. Sets the +#exception?+
|
||||
# flag to true.
|
||||
def exception(exception)
|
||||
@exception = true
|
||||
end
|
||||
|
||||
# Callback for the MSpec :after event. Performs the tag action
|
||||
# depending on the type of action and the outcome of evaluating
|
||||
# the example. See +TagAction+ for a description of the actions.
|
||||
def after(state)
|
||||
if self === state.description and outcome?
|
||||
tag = SpecTag.new
|
||||
tag.tag = @tag
|
||||
tag.comment = @comment
|
||||
tag.description = state.description
|
||||
|
||||
case @action
|
||||
when :add
|
||||
changed = MSpec.write_tag tag
|
||||
when :del
|
||||
changed = MSpec.delete_tag tag
|
||||
end
|
||||
|
||||
@report << state.description if changed
|
||||
end
|
||||
end
|
||||
|
||||
# Returns true if the result of evaluating the example matches
|
||||
# the _outcome_ registered for this tag action. See +TagAction+
|
||||
# for a description of the _outcome_ types.
|
||||
def outcome?
|
||||
@outcome == :all or
|
||||
(@outcome == :pass and not exception?) or
|
||||
(@outcome == :fail and exception?)
|
||||
end
|
||||
|
||||
# Returns true if an exception was raised while evaluating the
|
||||
# current example.
|
||||
def exception?
|
||||
@exception
|
||||
end
|
||||
|
||||
def report
|
||||
@report.join("\n") + "\n"
|
||||
end
|
||||
private :report
|
||||
|
||||
# Callback for the MSpec :finish event. Prints the actions
|
||||
# performed while evaluating the examples.
|
||||
def finish
|
||||
case @action
|
||||
when :add
|
||||
if @report.empty?
|
||||
print "\nTagAction: no specs were tagged with '#{@tag}'\n"
|
||||
else
|
||||
print "\nTagAction: specs tagged with '#{@tag}':\n\n"
|
||||
print report
|
||||
end
|
||||
when :del
|
||||
if @report.empty?
|
||||
print "\nTagAction: no tags '#{@tag}' were deleted\n"
|
||||
else
|
||||
print "\nTagAction: tag '#{@tag}' deleted for specs:\n\n"
|
||||
print report
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def register
|
||||
super
|
||||
MSpec.register :before, self
|
||||
MSpec.register :exception, self
|
||||
MSpec.register :after, self
|
||||
MSpec.register :finish, self
|
||||
end
|
||||
|
||||
def unregister
|
||||
super
|
||||
MSpec.unregister :before, self
|
||||
MSpec.unregister :exception, self
|
||||
MSpec.unregister :after, self
|
||||
MSpec.unregister :finish, self
|
||||
end
|
||||
end
|
56
spec/mspec/lib/mspec/runner/actions/taglist.rb
Normal file
56
spec/mspec/lib/mspec/runner/actions/taglist.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
require 'mspec/runner/actions/filter'
|
||||
|
||||
# TagListAction - prints out the descriptions for any specs
|
||||
# tagged with +tags+. If +tags+ is an empty list, prints out
|
||||
# descriptions for any specs that are tagged.
|
||||
class TagListAction
|
||||
def initialize(tags=nil)
|
||||
@tags = tags.nil? || tags.empty? ? nil : Array(tags)
|
||||
@filter = nil
|
||||
end
|
||||
|
||||
# Returns true. This enables us to match any tag when loading
|
||||
# tags from the file.
|
||||
def include?(arg)
|
||||
true
|
||||
end
|
||||
|
||||
# Returns true if any tagged descriptions matches +string+.
|
||||
def ===(string)
|
||||
@filter === string
|
||||
end
|
||||
|
||||
# Prints a banner about matching tagged specs.
|
||||
def start
|
||||
if @tags
|
||||
print "\nListing specs tagged with #{@tags.map { |t| "'#{t}'" }.join(", ") }\n\n"
|
||||
else
|
||||
print "\nListing all tagged specs\n\n"
|
||||
end
|
||||
end
|
||||
|
||||
# Creates a MatchFilter for specific tags or for all tags.
|
||||
def load
|
||||
@filter = nil
|
||||
desc = MSpec.read_tags(@tags || self).map { |t| t.description }
|
||||
@filter = MatchFilter.new(nil, *desc) unless desc.empty?
|
||||
end
|
||||
|
||||
# Prints the spec description if it matches the filter.
|
||||
def after(state)
|
||||
return unless self === state.description
|
||||
print state.description, "\n"
|
||||
end
|
||||
|
||||
def register
|
||||
MSpec.register :start, self
|
||||
MSpec.register :load, self
|
||||
MSpec.register :after, self
|
||||
end
|
||||
|
||||
def unregister
|
||||
MSpec.unregister :start, self
|
||||
MSpec.unregister :load, self
|
||||
MSpec.unregister :after, self
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue