mirror of
https://github.com/zhigang1992/RubyMotion.git
synced 2026-04-03 22:49:56 +08:00
add basic bacon/spec integration
This commit is contained in:
@@ -71,6 +71,13 @@ task :archive => ['build:ios'] do
|
||||
end
|
||||
end
|
||||
|
||||
desc "Run specs"
|
||||
task :spec do
|
||||
App.config.name += '_spec'
|
||||
App.config.spec_mode = true
|
||||
Rake::Task["simulator"].invoke
|
||||
end
|
||||
|
||||
desc "Deploy on the device"
|
||||
task :deploy => :archive do
|
||||
deploy = File.join(App.config.bindir, 'deploy')
|
||||
|
||||
@@ -38,62 +38,68 @@ module Motion; module Project;
|
||||
end
|
||||
|
||||
# Build object files.
|
||||
objs = []
|
||||
objs_build_dir = File.join(build_dir, config.sdk_version + '-sdk-objs')
|
||||
FileUtils.mkdir_p(objs_build_dir)
|
||||
project_file_changed = File.mtime(config.project_file) > File.mtime(objs_build_dir)
|
||||
config.ordered_build_files.each do |path|
|
||||
obj = File.join(objs_build_dir, "#{path}.o")
|
||||
build_file = Proc.new do |path|
|
||||
obj ||= File.join(objs_build_dir, "#{path}.o")
|
||||
should_rebuild = (project_file_changed \
|
||||
or !File.exist?(obj) \
|
||||
or File.mtime(path) > File.mtime(obj) \
|
||||
or File.mtime(ruby) > File.mtime(obj))
|
||||
|
||||
# Generate or retrieve init function.
|
||||
init_func = should_rebuild ? "MREP_#{`uuidgen`.strip.gsub('-', '')}" : `nm #{obj}`.scan(/T\s+_(MREP_.*)/)[0][0]
|
||||
objs << [obj, init_func]
|
||||
init_func = should_rebuild ? "MREP_#{`/usr/bin/uuidgen`.strip.gsub('-', '')}" : `/usr/bin/nm #{obj}`.scan(/T\s+_(MREP_.*)/)[0][0]
|
||||
|
||||
next unless should_rebuild
|
||||
|
||||
arch_objs = []
|
||||
archs.each do |arch|
|
||||
# Locate arch kernel.
|
||||
kernel = File.join(datadir, platform, "kernel-#{arch}.bc")
|
||||
raise "Can't locate kernel file" unless File.exist?(kernel)
|
||||
|
||||
# Prepare build_dir.
|
||||
bc = File.join(objs_build_dir, "#{path}.#{arch}.bc")
|
||||
FileUtils.mkdir_p(File.dirname(bc))
|
||||
|
||||
# LLVM bitcode.
|
||||
bs_flags = bs_files.map { |x| "--uses-bs \"" + x + "\" " }.join(' ')
|
||||
sh "/usr/bin/env VM_KERNEL_PATH=\"#{kernel}\" #{ruby} #{bs_flags} --emit-llvm \"#{bc}\" #{init_func} \"#{path}\""
|
||||
|
||||
# Assembly.
|
||||
asm = File.join(objs_build_dir, "#{path}.#{arch}.s")
|
||||
llc_arch = case arch
|
||||
when 'i386'; 'x86'
|
||||
when 'x86_64'; 'x86-64'
|
||||
when /^arm/; 'arm'
|
||||
else; arch
|
||||
if should_rebuild
|
||||
FileUtils.mkdir_p(File.dirname(obj))
|
||||
arch_objs = []
|
||||
archs.each do |arch|
|
||||
# Locate arch kernel.
|
||||
kernel = File.join(datadir, platform, "kernel-#{arch}.bc")
|
||||
raise "Can't locate kernel file" unless File.exist?(kernel)
|
||||
|
||||
# LLVM bitcode.
|
||||
bc = File.join(objs_build_dir, "#{path}.#{arch}.bc")
|
||||
bs_flags = bs_files.map { |x| "--uses-bs \"" + x + "\" " }.join(' ')
|
||||
sh "/usr/bin/env VM_KERNEL_PATH=\"#{kernel}\" #{ruby} #{bs_flags} --emit-llvm \"#{bc}\" #{init_func} \"#{path}\""
|
||||
|
||||
# Assembly.
|
||||
asm = File.join(objs_build_dir, "#{path}.#{arch}.s")
|
||||
llc_arch = case arch
|
||||
when 'i386'; 'x86'
|
||||
when 'x86_64'; 'x86-64'
|
||||
when /^arm/; 'arm'
|
||||
else; arch
|
||||
end
|
||||
sh "#{llc} \"#{bc}\" -o=\"#{asm}\" -march=#{llc_arch} -relocation-model=pic -disable-fp-elim -jit-enable-eh -disable-cfi"
|
||||
|
||||
# Object.
|
||||
arch_obj = File.join(objs_build_dir, "#{path}.#{arch}.o")
|
||||
sh "#{cc} -fexceptions -c -arch #{arch} \"#{asm}\" -o \"#{arch_obj}\""
|
||||
|
||||
arch_objs << arch_obj
|
||||
end
|
||||
sh "#{llc} \"#{bc}\" -o=\"#{asm}\" -march=#{llc_arch} -relocation-model=pic -disable-fp-elim -jit-enable-eh -disable-cfi"
|
||||
|
||||
# Object.
|
||||
arch_obj = File.join(objs_build_dir, "#{path}.#{arch}.o")
|
||||
sh "#{cc} -fexceptions -c -arch #{arch} \"#{asm}\" -o \"#{arch_obj}\""
|
||||
|
||||
arch_objs << arch_obj
|
||||
|
||||
# Assemble fat binary.
|
||||
arch_objs_list = arch_objs.map { |x| "\"#{x}\"" }.join(' ')
|
||||
sh "lipo -create #{arch_objs_list} -output \"#{obj}\""
|
||||
end
|
||||
|
||||
# Assemble fat binary.
|
||||
arch_objs_list = arch_objs.map { |x| "\"#{x}\"" }.join(' ')
|
||||
sh "lipo -create #{arch_objs_list} -output \"#{obj}\""
|
||||
|
||||
[obj, init_func]
|
||||
end
|
||||
objs = app_objs = config.ordered_build_files.map { |path| build_file.call(path) }
|
||||
if config.spec_mode
|
||||
# Build spec files too.
|
||||
objs << build_file.call(File.expand_path(File.join(File.dirname(__FILE__), '../spec.rb')))
|
||||
spec_objs = config.spec_files.map { |path| build_file.call(path) }
|
||||
objs += spec_objs
|
||||
end
|
||||
|
||||
# Generate main file.
|
||||
main_txt = <<EOS
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
extern "C" {
|
||||
void ruby_sysinit(int *, char ***);
|
||||
void ruby_init(void);
|
||||
@@ -112,6 +118,36 @@ EOS
|
||||
end
|
||||
main_txt << <<EOS
|
||||
}
|
||||
EOS
|
||||
|
||||
if config.spec_mode
|
||||
main_txt << <<EOS
|
||||
@interface SpecLauncher : NSObject
|
||||
@end
|
||||
|
||||
@implementation SpecLauncher
|
||||
|
||||
+ (id)launcher
|
||||
{
|
||||
SpecLauncher *launcher = [[self alloc] init];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:launcher selector:@selector(appLaunched:) name:UIApplicationDidFinishLaunchingNotification object:nil];
|
||||
return launcher;
|
||||
}
|
||||
|
||||
- (void)appLaunched:(id)notification
|
||||
{
|
||||
EOS
|
||||
spec_objs.each do |_, init_func|
|
||||
main_txt << "#{init_func}(self, 0);\n"
|
||||
end
|
||||
main_txt << "[NSClassFromString(@\"Bacon\") performSelector:@selector(run)];\n"
|
||||
main_txt << <<EOS
|
||||
}
|
||||
|
||||
@end
|
||||
EOS
|
||||
end
|
||||
main_txt << <<EOS
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
@@ -124,9 +160,10 @@ main(int argc, char **argv)
|
||||
try {
|
||||
void *self = rb_vm_top_self();
|
||||
EOS
|
||||
objs.each do |_, init_func|
|
||||
app_objs.each do |_, init_func|
|
||||
main_txt << "#{init_func}(self, 0);\n"
|
||||
end
|
||||
main_txt << "[SpecLauncher launcher];\n" if config.spec_mode
|
||||
main_txt << <<EOS
|
||||
retval = UIApplicationMain(argc, argv, nil, @"#{config.delegate_class}");
|
||||
rb_exit(retval);
|
||||
|
||||
@@ -25,10 +25,10 @@ module Motion; module Project
|
||||
end
|
||||
|
||||
variable :files, :platforms_dir, :sdk_version, :frameworks, :libs,
|
||||
:delegate_class, :name, :build_dir, :resources_dir, :identifier,
|
||||
:codesign_certificate, :provisioning_profile, :device_family,
|
||||
:interface_orientations, :version, :icons, :prerendered_icon,
|
||||
:seed_id, :entitlements
|
||||
:delegate_class, :name, :build_dir, :resources_dir, :specs_dir,
|
||||
:identifier, :codesign_certificate, :provisioning_profile,
|
||||
:device_family, :interface_orientations, :version, :icons,
|
||||
:prerendered_icon, :seed_id, :entitlements
|
||||
|
||||
def initialize(project_dir)
|
||||
@project_dir = project_dir
|
||||
@@ -41,6 +41,7 @@ module Motion; module Project
|
||||
@name = 'Untitled'
|
||||
@resources_dir = File.join(project_dir, 'resources')
|
||||
@build_dir = File.join(project_dir, 'build')
|
||||
@specs_dir = File.join(project_dir, 'spec')
|
||||
@device_family = :iphone
|
||||
@bundle_signature = '????'
|
||||
@interface_orientations = [:portrait, :landscape_left, :landscape_right]
|
||||
@@ -127,6 +128,12 @@ module Motion; module Project
|
||||
ary
|
||||
end
|
||||
|
||||
attr_accessor :spec_mode
|
||||
|
||||
def spec_files
|
||||
Dir.glob(File.join(specs_dir, '**', '*.rb'))
|
||||
end
|
||||
|
||||
def motiondir
|
||||
File.expand_path(File.join(File.dirname(__FILE__), '../../..'))
|
||||
end
|
||||
|
||||
550
lib/motion/spec.rb
Normal file
550
lib/motion/spec.rb
Normal file
@@ -0,0 +1,550 @@
|
||||
# Bacon -- small RSpec clone.
|
||||
#
|
||||
# "Truth will sooner come out from error than from confusion." ---Francis Bacon
|
||||
|
||||
# Copyright (C) 2007, 2008 Christian Neukirchen <purl.org/net/chneukirchen>
|
||||
#
|
||||
# Bacon is freely distributable under the terms of an MIT-style license.
|
||||
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
module Bacon
|
||||
VERSION = "1.3"
|
||||
|
||||
Counter = Hash.new(0)
|
||||
ErrorLog = ""
|
||||
Shared = Hash.new { |_, name|
|
||||
raise NameError, "no such context: #{name.inspect}"
|
||||
}
|
||||
|
||||
RestrictName = // unless defined? RestrictName
|
||||
RestrictContext = // unless defined? RestrictContext
|
||||
|
||||
Backtraces = true unless defined? Backtraces
|
||||
|
||||
module SpecDoxOutput
|
||||
def handle_specification_begin(name)
|
||||
puts spaces + name
|
||||
end
|
||||
|
||||
def handle_specification_end
|
||||
puts if Counter[:context_depth] == 1
|
||||
end
|
||||
|
||||
def handle_requirement_begin(description)
|
||||
print "#{spaces} - #{description}"
|
||||
end
|
||||
|
||||
def handle_requirement_end(error)
|
||||
puts error.empty? ? "" : " [#{error}]"
|
||||
end
|
||||
|
||||
def handle_summary
|
||||
print ErrorLog if Backtraces
|
||||
puts "%d specifications (%d requirements), %d failures, %d errors" %
|
||||
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
||||
end
|
||||
|
||||
def spaces
|
||||
" " * (Counter[:context_depth] - 1)
|
||||
end
|
||||
end
|
||||
|
||||
module TestUnitOutput
|
||||
def handle_specification_begin(name); end
|
||||
def handle_specification_end ; end
|
||||
|
||||
def handle_requirement_begin(description) end
|
||||
def handle_requirement_end(error)
|
||||
if error.empty?
|
||||
print "."
|
||||
else
|
||||
print error[0..0]
|
||||
end
|
||||
end
|
||||
|
||||
def handle_summary
|
||||
puts "", "Finished in #{Time.now - @timer} seconds."
|
||||
puts ErrorLog if Backtraces
|
||||
puts "%d tests, %d assertions, %d failures, %d errors" %
|
||||
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
||||
end
|
||||
end
|
||||
|
||||
module TapOutput
|
||||
def handle_specification_begin(name); end
|
||||
def handle_specification_end ; end
|
||||
|
||||
def handle_requirement_begin(description)
|
||||
ErrorLog.replace ""
|
||||
end
|
||||
|
||||
def handle_requirement_end(error)
|
||||
if error.empty?
|
||||
puts "ok %-3d - %s" % [Counter[:specifications], description]
|
||||
else
|
||||
puts "not ok %d - %s: %s" %
|
||||
[Counter[:specifications], description, error]
|
||||
puts ErrorLog.strip.gsub(/^/, '# ') if Backtraces
|
||||
end
|
||||
end
|
||||
|
||||
def handle_summary
|
||||
puts "1..#{Counter[:specifications]}"
|
||||
puts "# %d tests, %d assertions, %d failures, %d errors" %
|
||||
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
||||
end
|
||||
end
|
||||
|
||||
module KnockOutput
|
||||
def handle_specification_begin(name); end
|
||||
def handle_specification_end ; end
|
||||
|
||||
def handle_requirement_begin(description)
|
||||
ErrorLog.replace ""
|
||||
end
|
||||
|
||||
def handle_requirement_end(error)
|
||||
if error.empty?
|
||||
puts "ok - %s" % [description]
|
||||
else
|
||||
puts "not ok - %s: %s" % [description, error]
|
||||
puts ErrorLog.strip.gsub(/^/, '# ') if Backtraces
|
||||
end
|
||||
end
|
||||
|
||||
def handle_summary; end
|
||||
end
|
||||
|
||||
extend SpecDoxOutput # default
|
||||
|
||||
class Error < RuntimeError
|
||||
attr_accessor :count_as
|
||||
|
||||
def initialize(count_as, message)
|
||||
@count_as = count_as
|
||||
super message
|
||||
end
|
||||
end
|
||||
|
||||
class Specification
|
||||
attr_reader :description
|
||||
|
||||
def initialize(context, description, block, before_filters, after_filters)
|
||||
@context, @description, @block = context, description, block
|
||||
@before_filters, @after_filters = before_filters.dup, after_filters.dup
|
||||
|
||||
@postponed_blocks_count = 0
|
||||
@ran_spec_block = false
|
||||
@ran_after_filters = false
|
||||
@exception_occurred = false
|
||||
@error = ""
|
||||
end
|
||||
|
||||
def postponed?
|
||||
@postponed_blocks_count != 0
|
||||
end
|
||||
|
||||
def run_before_filters
|
||||
execute_block { @before_filters.each { |f| @context.instance_eval(&f) } }
|
||||
end
|
||||
|
||||
def run_spec_block
|
||||
@ran_spec_block = true
|
||||
# If an exception occurred, we definitely don't need to perform the actual spec anymore
|
||||
unless @exception_occurred
|
||||
execute_block { @context.instance_eval(&@block) }
|
||||
end
|
||||
finish_spec unless postponed?
|
||||
end
|
||||
|
||||
def run_after_filters
|
||||
@ran_after_filters = true
|
||||
execute_block { @after_filters.each { |f| @context.instance_eval(&f) } }
|
||||
end
|
||||
|
||||
def run
|
||||
Bacon.handle_requirement_begin(@description)
|
||||
Counter[:depth] += 1
|
||||
run_before_filters
|
||||
@number_of_requirements_before = Counter[:requirements]
|
||||
run_spec_block unless postponed?
|
||||
end
|
||||
|
||||
def schedule_block(seconds, &block)
|
||||
# If an exception occurred, we definitely don't need to schedule any more blocks
|
||||
unless @exception_occurred
|
||||
@postponed_blocks_count += 1
|
||||
performSelector("run_postponed_block:", withObject:block, afterDelay:seconds)
|
||||
end
|
||||
end
|
||||
|
||||
def postpone_block(timeout = 1, &block)
|
||||
# If an exception occurred, we definitely don't need to schedule any more blocks
|
||||
unless @exception_occurred
|
||||
if @postponed_block
|
||||
raise "Only one indefinite `wait' block at the same time is allowed!"
|
||||
else
|
||||
@postponed_blocks_count += 1
|
||||
@postponed_block = block
|
||||
performSelector("postponed_block_timeout_exceeded", withObject:nil, afterDelay:timeout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def postpone_block_until_change(object_to_observe, key_path, timeout = 1, &block)
|
||||
# If an exception occurred, we definitely don't need to schedule any more blocks
|
||||
unless @exception_occurred
|
||||
if @postponed_block
|
||||
raise "Only one indefinite `wait' block at the same time is allowed!"
|
||||
else
|
||||
@postponed_blocks_count += 1
|
||||
@postponed_block = block
|
||||
@observed_object_and_key_path = [object_to_observe, key_path]
|
||||
object_to_observe.addObserver(self, forKeyPath:key_path, options:0, context:nil)
|
||||
performSelector("postponed_change_block_timeout_exceeded", withObject:nil, afterDelay:timeout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def observeValueForKeyPath(key_path, ofObject:object, change:_, context:__)
|
||||
resume
|
||||
end
|
||||
|
||||
def postponed_change_block_timeout_exceeded
|
||||
remove_observer!
|
||||
postponed_block_timeout_exceeded
|
||||
end
|
||||
|
||||
def remove_observer!
|
||||
if @observed_object_and_key_path
|
||||
object, key_path = @observed_object_and_key_path
|
||||
object.removeObserver(self, forKeyPath:key_path)
|
||||
@observed_object_and_key_path = nil
|
||||
end
|
||||
end
|
||||
|
||||
def postponed_block_timeout_exceeded
|
||||
cancel_scheduled_requests!
|
||||
execute_block { raise Error.new(:failed, "timeout exceeded: #{@context.name} - #{@description}") }
|
||||
@postponed_blocks_count = 0
|
||||
finish_spec
|
||||
end
|
||||
|
||||
def resume
|
||||
NSObject.cancelPreviousPerformRequestsWithTarget(self, selector:'postponed_block_timeout_exceeded', object:nil)
|
||||
NSObject.cancelPreviousPerformRequestsWithTarget(self, selector:'postponed_change_block_timeout_exceeded', object:nil)
|
||||
remove_observer!
|
||||
block, @postponed_block = @postponed_block, nil
|
||||
run_postponed_block(block)
|
||||
end
|
||||
|
||||
def run_postponed_block(block)
|
||||
# If an exception occurred, we definitely don't need execute any more blocks
|
||||
execute_block(&block) unless @exception_occurred
|
||||
@postponed_blocks_count -= 1
|
||||
unless postponed?
|
||||
if @ran_after_filters
|
||||
exit_spec
|
||||
elsif @ran_spec_block
|
||||
finish_spec
|
||||
else
|
||||
run_spec_block
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def finish_spec
|
||||
if !@exception_occurred && Counter[:requirements] == @number_of_requirements_before
|
||||
# the specification did not contain any requirements, so it flunked
|
||||
execute_block { raise Error.new(:missing, "empty specification: #{@context.name} #{@description}") }
|
||||
end
|
||||
run_after_filters
|
||||
exit_spec unless postponed?
|
||||
end
|
||||
|
||||
def cancel_scheduled_requests!
|
||||
NSObject.cancelPreviousPerformRequestsWithTarget(@context)
|
||||
NSObject.cancelPreviousPerformRequestsWithTarget(self)
|
||||
end
|
||||
|
||||
def exit_spec
|
||||
cancel_scheduled_requests!
|
||||
Counter[:depth] -= 1
|
||||
Bacon.handle_requirement_end(@error)
|
||||
@context.specification_did_finish(self)
|
||||
end
|
||||
|
||||
def execute_block
|
||||
begin
|
||||
yield
|
||||
rescue Object => e
|
||||
@exception_occurred = true
|
||||
|
||||
ErrorLog << "#{e.class}: #{e.message}\n"
|
||||
lines = $DEBUG ? e.backtrace : e.backtrace.find_all { |line| line !~ /bin\/macbacon|\/mac_bacon\.rb:\d+/ }
|
||||
lines.each_with_index { |line, i|
|
||||
ErrorLog << "\t#{line}#{i==0 ? ": #{@context.name} - #{@description}" : ""}\n"
|
||||
}
|
||||
ErrorLog << "\n"
|
||||
|
||||
@error = if e.kind_of? Error
|
||||
Counter[e.count_as] += 1
|
||||
e.count_as.to_s.upcase
|
||||
else
|
||||
Counter[:errors] += 1
|
||||
"ERROR: #{e.class}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.add_context(context)
|
||||
(@contexts ||= []) << context
|
||||
end
|
||||
|
||||
def self.current_context_index
|
||||
@current_context_index ||= 0
|
||||
end
|
||||
|
||||
def self.current_context
|
||||
@contexts[current_context_index]
|
||||
end
|
||||
|
||||
def self.run
|
||||
@timer ||= Time.now
|
||||
Counter[:context_depth] += 1
|
||||
handle_specification_begin(current_context.name)
|
||||
current_context.performSelector("run", withObject:nil, afterDelay:0)
|
||||
end
|
||||
|
||||
def self.context_did_finish(context)
|
||||
handle_specification_end
|
||||
Counter[:context_depth] -= 1
|
||||
if (@current_context_index + 1) < @contexts.size
|
||||
@current_context_index += 1
|
||||
run
|
||||
else
|
||||
# DONE
|
||||
handle_summary
|
||||
exit(Counter.values_at(:failed, :errors).inject(:+))
|
||||
end
|
||||
end
|
||||
|
||||
class Context
|
||||
attr_reader :name, :block
|
||||
|
||||
def initialize(name, before = nil, after = nil, &block)
|
||||
@name = name
|
||||
@before, @after = (before ? before.dup : []), (after ? after.dup : [])
|
||||
@block = block
|
||||
@specifications = []
|
||||
@current_specification_index = 0
|
||||
|
||||
Bacon.add_context(self)
|
||||
|
||||
instance_eval(&block)
|
||||
end
|
||||
|
||||
def run
|
||||
# TODO
|
||||
#return unless name =~ RestrictContext
|
||||
if spec = current_specification
|
||||
spec.performSelector("run", withObject:nil, afterDelay:0)
|
||||
else
|
||||
Bacon.context_did_finish(self)
|
||||
end
|
||||
end
|
||||
|
||||
def current_specification
|
||||
@specifications[@current_specification_index]
|
||||
end
|
||||
|
||||
def specification_did_finish(spec)
|
||||
if (@current_specification_index + 1) < @specifications.size
|
||||
@current_specification_index += 1
|
||||
run
|
||||
else
|
||||
Bacon.context_did_finish(self)
|
||||
end
|
||||
end
|
||||
|
||||
def before(&block); @before << block; end
|
||||
def after(&block); @after << block; end
|
||||
|
||||
def behaves_like(*names)
|
||||
names.each { |name| instance_eval(&Shared[name]) }
|
||||
end
|
||||
|
||||
def it(description, &block)
|
||||
return unless description =~ RestrictName
|
||||
block ||= lambda { should.flunk "not implemented" }
|
||||
Counter[:specifications] += 1
|
||||
@specifications << Specification.new(self, description, block, @before, @after)
|
||||
end
|
||||
|
||||
def should(*args, &block)
|
||||
if Counter[:depth]==0
|
||||
it('should '+args.first,&block)
|
||||
else
|
||||
super(*args,&block)
|
||||
end
|
||||
end
|
||||
|
||||
def describe(*args, &block)
|
||||
context = Bacon::Context.new(args.join(' '), @before, @after, &block)
|
||||
(parent_context = self).methods(false).each {|e|
|
||||
class<<context; self end.send(:define_method, e) {|*args| parent_context.send(e, *args)}
|
||||
}
|
||||
context
|
||||
end
|
||||
|
||||
def wait(seconds = nil, &block)
|
||||
if seconds
|
||||
current_specification.schedule_block(seconds, &block)
|
||||
else
|
||||
current_specification.postpone_block(&block)
|
||||
end
|
||||
end
|
||||
|
||||
def wait_max(timeout, &block)
|
||||
current_specification.postpone_block(timeout, &block)
|
||||
end
|
||||
|
||||
def wait_for_change(object_to_observe, key_path, timeout = 1, &block)
|
||||
current_specification.postpone_block_until_change(object_to_observe, key_path, timeout, &block)
|
||||
end
|
||||
|
||||
def resume
|
||||
current_specification.resume
|
||||
end
|
||||
|
||||
def raise?(*args, &block); block.raise?(*args); end
|
||||
def throw?(*args, &block); block.throw?(*args); end
|
||||
def change?(*args, &block); block.change?(*args); end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Object
|
||||
def true?; false; end
|
||||
def false?; false; end
|
||||
end
|
||||
|
||||
class TrueClass
|
||||
def true?; true; end
|
||||
end
|
||||
|
||||
class FalseClass
|
||||
def false?; true; end
|
||||
end
|
||||
|
||||
class Proc
|
||||
def raise?(*exceptions)
|
||||
call
|
||||
rescue *(exceptions.empty? ? RuntimeError : exceptions) => e
|
||||
e
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
def throw?(sym)
|
||||
catch(sym) {
|
||||
call
|
||||
return false
|
||||
}
|
||||
return true
|
||||
end
|
||||
|
||||
def change?
|
||||
pre_result = yield
|
||||
called = call
|
||||
post_result = yield
|
||||
pre_result != post_result
|
||||
end
|
||||
end
|
||||
|
||||
class Numeric
|
||||
def close?(to, delta)
|
||||
(to.to_f - self).abs <= delta.to_f rescue false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Object
|
||||
def should(*args, &block) Should.new(self).be(*args, &block) end
|
||||
end
|
||||
|
||||
module Kernel
|
||||
private
|
||||
def describe(*args, &block) Bacon::Context.new(args.join(' '), &block) end
|
||||
def shared(name, &block) Bacon::Shared[name] = block end
|
||||
end
|
||||
|
||||
class Should
|
||||
# Kills ==, ===, =~, eql?, equal?, frozen?, instance_of?, is_a?,
|
||||
# kind_of?, nil?, respond_to?, tainted?
|
||||
instance_methods.each { |name| undef_method name if name =~ /\?|^\W+$/ }
|
||||
|
||||
def initialize(object)
|
||||
@object = object
|
||||
@negated = false
|
||||
end
|
||||
|
||||
def not(*args, &block)
|
||||
@negated = !@negated
|
||||
|
||||
if args.empty?
|
||||
self
|
||||
else
|
||||
be(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def be(*args, &block)
|
||||
if args.empty?
|
||||
self
|
||||
else
|
||||
block = args.shift unless block_given?
|
||||
satisfy(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
alias a be
|
||||
alias an be
|
||||
|
||||
def satisfy(*args, &block)
|
||||
if args.size == 1 && String === args.first
|
||||
description = args.shift
|
||||
else
|
||||
description = ""
|
||||
end
|
||||
|
||||
r = yield(@object, *args)
|
||||
if Bacon::Counter[:depth] > 0
|
||||
Bacon::Counter[:requirements] += 1
|
||||
raise Bacon::Error.new(:failed, description) unless @negated ^ r
|
||||
r
|
||||
else
|
||||
@negated ? !r : !!r
|
||||
end
|
||||
end
|
||||
|
||||
def method_missing(name, *args, &block)
|
||||
name = "#{name}?" if name.to_s =~ /\w[^?]\z/
|
||||
|
||||
desc = @negated ? "not " : ""
|
||||
desc << @object.inspect << "." << name.to_s
|
||||
desc << "(" << args.map{|x|x.inspect}.join(", ") << ") failed"
|
||||
|
||||
satisfy(desc) { |x| x.__send__(name, *args, &block) }
|
||||
end
|
||||
|
||||
def equal(value) self == value end
|
||||
def match(value) self =~ value end
|
||||
def identical_to(value) self.equal? value end
|
||||
alias same_as identical_to
|
||||
|
||||
def flunk(reason="Flunked")
|
||||
raise Bacon::Error.new(:failed, reason)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user