mac_bacon 1.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +18 -0
- data/ChangeLog +272 -0
- data/RDOX +96 -0
- data/README +324 -0
- data/Rakefile +145 -0
- data/bin/macbacon +118 -0
- data/lib/mac_bacon.rb +450 -0
- data/test/spec_bacon.rb +390 -0
- data/test/spec_mac_bacon.rb +20 -0
- data/test/spec_nontrue.rb +16 -0
- data/test/spec_should.rb +33 -0
- metadata +86 -0
data/Rakefile
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# Rakefile for Bacon. -*-ruby-*-
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
|
6
|
+
desc "Run all the tests"
|
7
|
+
task :default => [:test]
|
8
|
+
|
9
|
+
desc "Do predistribution stuff"
|
10
|
+
task :predist => [:chmod, :changelog, :rdoc]
|
11
|
+
|
12
|
+
|
13
|
+
desc "Make an archive as .tar.gz"
|
14
|
+
task :dist => [:test, :predist] do
|
15
|
+
sh "git archive --format=tar --prefix=#{release}/ HEAD^{tree} >#{release}.tar"
|
16
|
+
sh "pax -waf #{release}.tar -s ':^:#{release}/:' RDOX ChangeLog doc"
|
17
|
+
sh "gzip -f -9 #{release}.tar"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Helper to retrieve the "revision number" of the git tree.
|
21
|
+
def git_tree_version
|
22
|
+
if File.directory?(".git")
|
23
|
+
@tree_version ||= `git describe`.strip.sub('-', '.')
|
24
|
+
@tree_version << ".0" unless @tree_version.count('.') == 2
|
25
|
+
else
|
26
|
+
#$: << "lib"
|
27
|
+
#require 'mac_bacon'
|
28
|
+
#@tree_version = Bacon::VERSION
|
29
|
+
@tree_version = "1.1"
|
30
|
+
end
|
31
|
+
@tree_version
|
32
|
+
end
|
33
|
+
|
34
|
+
def gem_version
|
35
|
+
git_tree_version.gsub(/-.*/, '')
|
36
|
+
end
|
37
|
+
|
38
|
+
def release
|
39
|
+
"macbacon-#{git_tree_version}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def manifest
|
43
|
+
`git ls-files`.split("\n") - [".gitignore"]
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
desc "Make binaries executable"
|
48
|
+
task :chmod do
|
49
|
+
Dir["bin/*"].each { |binary| File.chmod(0775, binary) }
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Generate a ChangeLog"
|
53
|
+
task :changelog do
|
54
|
+
File.open("ChangeLog", "w") { |out|
|
55
|
+
`git log -z`.split("\0").map { |chunk|
|
56
|
+
author = chunk[/Author: (.*)/, 1].strip
|
57
|
+
date = chunk[/Date: (.*)/, 1].strip
|
58
|
+
desc, detail = $'.strip.split("\n", 2)
|
59
|
+
detail ||= ""
|
60
|
+
detail = detail.gsub(/.*darcs-hash:.*/, '')
|
61
|
+
detail.rstrip!
|
62
|
+
out.puts "#{date} #{author}"
|
63
|
+
out.puts " * #{desc.strip}"
|
64
|
+
out.puts detail unless detail.empty?
|
65
|
+
out.puts
|
66
|
+
}
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
desc "Generate RDox"
|
72
|
+
task "RDOX" do
|
73
|
+
sh "macruby -Ilib bin/macbacon --automatic --specdox >RDOX"
|
74
|
+
end
|
75
|
+
|
76
|
+
desc "Run all the tests"
|
77
|
+
task :test do
|
78
|
+
sh "macruby -Ilib bin/macbacon --automatic --quiet"
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
begin
|
83
|
+
$" << "sources" if defined? FromSrc
|
84
|
+
require 'rubygems'
|
85
|
+
|
86
|
+
require 'rake'
|
87
|
+
require 'rake/clean'
|
88
|
+
require 'rake/packagetask'
|
89
|
+
require 'rake/gempackagetask'
|
90
|
+
require 'fileutils'
|
91
|
+
rescue LoadError
|
92
|
+
# Too bad.
|
93
|
+
else
|
94
|
+
spec = Gem::Specification.new do |s|
|
95
|
+
s.name = "mac_bacon"
|
96
|
+
s.version = gem_version
|
97
|
+
s.platform = Gem::Platform::RUBY
|
98
|
+
s.summary = "a small RSpec clone for MacRuby"
|
99
|
+
|
100
|
+
s.description = <<-EOF
|
101
|
+
Bacon is a small RSpec clone weighing less than 350 LoC but
|
102
|
+
nevertheless providing all essential features.
|
103
|
+
|
104
|
+
This MacBacon fork differs with regular Bacon in that it operates
|
105
|
+
properly in a NSRunloop based environment. I.e. MacRuby/Objective-C.
|
106
|
+
|
107
|
+
https://github.com/alloy/MacBacon
|
108
|
+
EOF
|
109
|
+
|
110
|
+
s.files = manifest + %w(RDOX ChangeLog)
|
111
|
+
s.bindir = 'bin'
|
112
|
+
s.executables << 'macbacon'
|
113
|
+
s.require_path = 'lib'
|
114
|
+
s.has_rdoc = true
|
115
|
+
s.extra_rdoc_files = ['README', 'RDOX']
|
116
|
+
s.test_files = []
|
117
|
+
|
118
|
+
s.author = 'Eloy Durán'
|
119
|
+
s.email = 'eloy.de.enige@gmail.com'
|
120
|
+
s.homepage = 'https://github.com/alloy/MacBacon'
|
121
|
+
end
|
122
|
+
|
123
|
+
#task :gem => [:chmod, :changelog]
|
124
|
+
task :gem => [:chmod]
|
125
|
+
|
126
|
+
Rake::GemPackageTask.new(spec) do |p|
|
127
|
+
p.gem_spec = spec
|
128
|
+
p.need_tar = false
|
129
|
+
p.need_zip = false
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
desc "Generate RDoc documentation"
|
134
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
135
|
+
rdoc.options << '--line-numbers' << '--inline-source' <<
|
136
|
+
'--main' << 'README' <<
|
137
|
+
'--title' << 'Bacon Documentation' <<
|
138
|
+
'--charset' << 'utf-8'
|
139
|
+
rdoc.rdoc_dir = "doc"
|
140
|
+
rdoc.rdoc_files.include 'README'
|
141
|
+
rdoc.rdoc_files.include 'COPYING'
|
142
|
+
rdoc.rdoc_files.include 'RDOX'
|
143
|
+
rdoc.rdoc_files.include('lib/mac_bacon.rb')
|
144
|
+
end
|
145
|
+
task :rdoc => ["RDOX"]
|
data/bin/macbacon
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '../lib/')
|
6
|
+
module Bacon; end
|
7
|
+
|
8
|
+
automatic = false
|
9
|
+
output = 'SpecDoxOutput'
|
10
|
+
|
11
|
+
opts = OptionParser.new("", 24, ' ') { |opts|
|
12
|
+
opts.banner = "Usage: macbacon [options] [files | -a] [-- untouched arguments]"
|
13
|
+
|
14
|
+
opts.separator ""
|
15
|
+
opts.separator "Ruby options:"
|
16
|
+
|
17
|
+
lineno = 1
|
18
|
+
opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
|
19
|
+
eval line, TOPLEVEL_BINDING, "-e", lineno
|
20
|
+
lineno += 1
|
21
|
+
}
|
22
|
+
|
23
|
+
opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
|
24
|
+
$DEBUG = true
|
25
|
+
}
|
26
|
+
opts.on("-w", "--warn", "turn warnings on for your script") {
|
27
|
+
$-w = true
|
28
|
+
}
|
29
|
+
|
30
|
+
opts.on("-I", "--include PATH",
|
31
|
+
"specify $LOAD_PATH (may be used more than once)") { |path|
|
32
|
+
$LOAD_PATH.unshift(*path.split(":"))
|
33
|
+
}
|
34
|
+
|
35
|
+
opts.on("-r", "--require LIBRARY",
|
36
|
+
"require the library, before executing your script") { |library|
|
37
|
+
require library
|
38
|
+
}
|
39
|
+
|
40
|
+
opts.separator ""
|
41
|
+
opts.separator "bacon options:"
|
42
|
+
|
43
|
+
opts.on("-s", "--specdox", "do AgileDox-like output (default)") {
|
44
|
+
output = 'SpecDoxOutput'
|
45
|
+
}
|
46
|
+
opts.on("-q", "--quiet", "do Test::Unit-like non-verbose output") {
|
47
|
+
output = 'TestUnitOutput'
|
48
|
+
}
|
49
|
+
opts.on("-p", "--tap", "do TAP (Test Anything Protocol) output") {
|
50
|
+
output = 'TapOutput'
|
51
|
+
}
|
52
|
+
opts.on("-k", "--knock", "do Knock output") {
|
53
|
+
output = 'KnockOutput'
|
54
|
+
}
|
55
|
+
|
56
|
+
opts.on("-o", "--output FORMAT",
|
57
|
+
"do FORMAT (SpecDox/TestUnit/Tap) output") { |format|
|
58
|
+
output = format + "Output"
|
59
|
+
}
|
60
|
+
opts.on("-Q", "--no-backtrace", "don't print backtraces") {
|
61
|
+
Bacon.const_set :Backtraces, false
|
62
|
+
}
|
63
|
+
|
64
|
+
opts.on("-a", "--automatic", "gather tests from ./test/, include ./lib/") {
|
65
|
+
$LOAD_PATH.unshift "lib" if File.directory? "lib"
|
66
|
+
automatic = true
|
67
|
+
}
|
68
|
+
|
69
|
+
opts.on('-n', '--name NAME', String,
|
70
|
+
"runs tests matching regexp NAME") { |n|
|
71
|
+
Bacon.const_set :RestrictName, Regexp.new(n)
|
72
|
+
}
|
73
|
+
|
74
|
+
opts.on('-t', '--testcase TESTCASE', String,
|
75
|
+
"runs tests in TestCases matching regexp TESTCASE") { |t|
|
76
|
+
Bacon.const_set :RestrictContext, Regexp.new(t)
|
77
|
+
}
|
78
|
+
|
79
|
+
opts.separator ""
|
80
|
+
opts.separator "Common options:"
|
81
|
+
|
82
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
83
|
+
puts opts
|
84
|
+
exit
|
85
|
+
end
|
86
|
+
|
87
|
+
opts.on_tail("--version", "Show version") do
|
88
|
+
require 'mac_bacon'
|
89
|
+
puts "bacon #{Bacon::VERSION}"
|
90
|
+
exit
|
91
|
+
end
|
92
|
+
|
93
|
+
opts.parse! ARGV
|
94
|
+
}
|
95
|
+
|
96
|
+
files = ARGV
|
97
|
+
|
98
|
+
if automatic
|
99
|
+
files.concat Dir["test/**/test_*.rb"]
|
100
|
+
files.concat Dir["test/**/spec_*.rb"]
|
101
|
+
files.concat Dir["spec/**/spec_*.rb"]
|
102
|
+
files.concat Dir["spec/**/*_spec.rb"]
|
103
|
+
end
|
104
|
+
|
105
|
+
if files.empty?
|
106
|
+
puts opts.banner
|
107
|
+
exit 1
|
108
|
+
end
|
109
|
+
|
110
|
+
require 'mac_bacon'
|
111
|
+
|
112
|
+
Bacon.extend Bacon.const_get(output) rescue abort "No such formatter: #{output}"
|
113
|
+
|
114
|
+
files.each { |file|
|
115
|
+
load file
|
116
|
+
}
|
117
|
+
|
118
|
+
Bacon.run
|
data/lib/mac_bacon.rb
ADDED
@@ -0,0 +1,450 @@
|
|
1
|
+
# Bacon -- small RSpec clone.
|
2
|
+
#
|
3
|
+
# "Truth will sooner come out from error than from confusion." ---Francis Bacon
|
4
|
+
|
5
|
+
# Copyright (C) 2007, 2008 Christian Neukirchen <purl.org/net/chneukirchen>
|
6
|
+
#
|
7
|
+
# Bacon is freely distributable under the terms of an MIT-style license.
|
8
|
+
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
9
|
+
|
10
|
+
framework "Cocoa"
|
11
|
+
|
12
|
+
module Bacon
|
13
|
+
VERSION = "1.1"
|
14
|
+
|
15
|
+
Counter = Hash.new(0)
|
16
|
+
ErrorLog = ""
|
17
|
+
Shared = Hash.new { |_, name|
|
18
|
+
raise NameError, "no such context: #{name.inspect}"
|
19
|
+
}
|
20
|
+
|
21
|
+
RestrictName = // unless defined? RestrictName
|
22
|
+
RestrictContext = // unless defined? RestrictContext
|
23
|
+
|
24
|
+
Backtraces = true unless defined? Backtraces
|
25
|
+
|
26
|
+
module SpecDoxOutput
|
27
|
+
def handle_specification_begin(name)
|
28
|
+
puts spaces + name
|
29
|
+
end
|
30
|
+
|
31
|
+
def handle_specification_end
|
32
|
+
puts if Counter[:context_depth] == 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def handle_requirement_begin(description)
|
36
|
+
print "#{spaces} - #{description}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def handle_requirement_end(error)
|
40
|
+
puts error.empty? ? "" : " [#{error}]"
|
41
|
+
end
|
42
|
+
|
43
|
+
def handle_summary
|
44
|
+
print ErrorLog if Backtraces
|
45
|
+
puts "%d specifications (%d requirements), %d failures, %d errors" %
|
46
|
+
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
47
|
+
end
|
48
|
+
|
49
|
+
def spaces
|
50
|
+
" " * (Counter[:context_depth] - 1)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module TestUnitOutput
|
55
|
+
def handle_specification_begin(name); end
|
56
|
+
def handle_specification_end ; end
|
57
|
+
|
58
|
+
def handle_requirement_begin(description) end
|
59
|
+
def handle_requirement_end(error)
|
60
|
+
if error.empty?
|
61
|
+
print "."
|
62
|
+
else
|
63
|
+
print error[0..0]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def handle_summary
|
68
|
+
puts "", "Finished in #{Time.now - @timer} seconds."
|
69
|
+
puts ErrorLog if Backtraces
|
70
|
+
puts "%d tests, %d assertions, %d failures, %d errors" %
|
71
|
+
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
module TapOutput
|
76
|
+
def handle_specification_begin(name); end
|
77
|
+
def handle_specification_end ; end
|
78
|
+
|
79
|
+
def handle_requirement_begin(description)
|
80
|
+
ErrorLog.replace ""
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_requirement_end(error)
|
84
|
+
if error.empty?
|
85
|
+
puts "ok %-3d - %s" % [Counter[:specifications], description]
|
86
|
+
else
|
87
|
+
puts "not ok %d - %s: %s" %
|
88
|
+
[Counter[:specifications], description, error]
|
89
|
+
puts ErrorLog.strip.gsub(/^/, '# ') if Backtraces
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_summary
|
94
|
+
puts "1..#{Counter[:specifications]}"
|
95
|
+
puts "# %d tests, %d assertions, %d failures, %d errors" %
|
96
|
+
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module KnockOutput
|
101
|
+
def handle_specification_begin(name); end
|
102
|
+
def handle_specification_end ; end
|
103
|
+
|
104
|
+
def handle_requirement_begin(description)
|
105
|
+
ErrorLog.replace ""
|
106
|
+
end
|
107
|
+
|
108
|
+
def handle_requirement_end(error)
|
109
|
+
if error.empty?
|
110
|
+
puts "ok - %s" % [description]
|
111
|
+
else
|
112
|
+
puts "not ok - %s: %s" % [description, error]
|
113
|
+
puts ErrorLog.strip.gsub(/^/, '# ') if Backtraces
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def handle_summary; end
|
118
|
+
end
|
119
|
+
|
120
|
+
extend SpecDoxOutput # default
|
121
|
+
|
122
|
+
class Error < RuntimeError
|
123
|
+
attr_accessor :count_as
|
124
|
+
|
125
|
+
def initialize(count_as, message)
|
126
|
+
@count_as = count_as
|
127
|
+
super message
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Specification
|
132
|
+
attr_reader :description
|
133
|
+
|
134
|
+
def initialize(context, description, block, before_filters, after_filters)
|
135
|
+
@context, @description, @block = context, description, block
|
136
|
+
@before_filters, @after_filters = before_filters.dup, after_filters.dup
|
137
|
+
|
138
|
+
@postponed_blocks_count = 0
|
139
|
+
@exception_occurred = false
|
140
|
+
@error = ""
|
141
|
+
end
|
142
|
+
|
143
|
+
def run_before_filters
|
144
|
+
@before_filters.each { |f| @context.instance_eval(&f) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def run_after_filters
|
148
|
+
@after_filters.each { |f| @context.instance_eval(&f) }
|
149
|
+
end
|
150
|
+
|
151
|
+
def run
|
152
|
+
Bacon.handle_requirement_begin(@description)
|
153
|
+
execute_block do
|
154
|
+
Counter[:depth] += 1
|
155
|
+
run_before_filters
|
156
|
+
@number_of_requirements_before = Counter[:requirements]
|
157
|
+
@context.instance_eval(&@block)
|
158
|
+
end
|
159
|
+
|
160
|
+
finalize if @postponed_blocks_count == 0
|
161
|
+
end
|
162
|
+
|
163
|
+
def postpone_block(seconds, &block)
|
164
|
+
# If an exception occurred, we definitely don't need to schedule any more blocks
|
165
|
+
unless @exception_occurred
|
166
|
+
@postponed_blocks_count += 1
|
167
|
+
performSelector("run_postponed_block:", withObject:block, afterDelay:seconds)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def run_postponed_block(block)
|
172
|
+
# If an exception occurred, we definitely don't need execute any more blocks
|
173
|
+
execute_block(&block) unless @exception_occurred
|
174
|
+
@postponed_blocks_count -= 1
|
175
|
+
finalize if @postponed_blocks_count == 0
|
176
|
+
end
|
177
|
+
|
178
|
+
def finalize
|
179
|
+
if Counter[:requirements] == @number_of_requirements_before
|
180
|
+
# the specification did not contain any requirements, so it flunked
|
181
|
+
# TODO ugh, exceptions for control flow, need to clean this up
|
182
|
+
execute_block { raise Error.new(:missing, "empty specification: #{@context.name} #{@description}") }
|
183
|
+
end
|
184
|
+
|
185
|
+
execute_block { run_after_filters }
|
186
|
+
|
187
|
+
Counter[:depth] -= 1
|
188
|
+
Bacon.handle_requirement_end(@error)
|
189
|
+
@context.specification_did_finish(self)
|
190
|
+
end
|
191
|
+
|
192
|
+
def execute_block
|
193
|
+
begin
|
194
|
+
yield
|
195
|
+
rescue Object => e
|
196
|
+
@exception_occurred = true
|
197
|
+
|
198
|
+
ErrorLog << "#{e.class}: #{e.message}\n"
|
199
|
+
e.backtrace.find_all { |line| line !~ /bin\/bacon|\/bacon\.rb:\d+/ }.
|
200
|
+
each_with_index { |line, i|
|
201
|
+
ErrorLog << "\t#{line}#{i==0 ? ": #{@context.name} - #{@description}" : ""}\n"
|
202
|
+
}
|
203
|
+
ErrorLog << "\n"
|
204
|
+
|
205
|
+
@error = if e.kind_of? Error
|
206
|
+
Counter[e.count_as] += 1
|
207
|
+
e.count_as.to_s.upcase
|
208
|
+
else
|
209
|
+
Counter[:errors] += 1
|
210
|
+
"ERROR: #{e.class}"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def self.add_context(context)
|
217
|
+
(@contexts ||= []) << context
|
218
|
+
end
|
219
|
+
|
220
|
+
def self.current_context_index
|
221
|
+
@current_context_index ||= 0
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.current_context
|
225
|
+
@contexts[current_context_index]
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.run
|
229
|
+
@timer ||= Time.now
|
230
|
+
Counter[:context_depth] += 1
|
231
|
+
handle_specification_begin(current_context.name)
|
232
|
+
current_context.performSelector("run", withObject:nil, afterDelay:0)
|
233
|
+
NSApplication.sharedApplication.run
|
234
|
+
end
|
235
|
+
|
236
|
+
def self.context_did_finish(context)
|
237
|
+
handle_specification_end
|
238
|
+
Counter[:context_depth] -= 1
|
239
|
+
if (@current_context_index + 1) < @contexts.size
|
240
|
+
@current_context_index += 1
|
241
|
+
run
|
242
|
+
else
|
243
|
+
# DONE
|
244
|
+
handle_summary
|
245
|
+
NSApplication.sharedApplication.terminate(self)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
class Context
|
250
|
+
attr_reader :name, :block
|
251
|
+
|
252
|
+
def initialize(name, before = nil, after = nil, &block)
|
253
|
+
@name = name
|
254
|
+
@before, @after = (before ? before.dup : []), (after ? after.dup : [])
|
255
|
+
@block = block
|
256
|
+
@specifications = []
|
257
|
+
@current_specification_index = 0
|
258
|
+
|
259
|
+
Bacon.add_context(self)
|
260
|
+
|
261
|
+
instance_eval(&block)
|
262
|
+
end
|
263
|
+
|
264
|
+
def run
|
265
|
+
# TODO
|
266
|
+
#return unless name =~ RestrictContext
|
267
|
+
if spec = current_specification
|
268
|
+
spec.performSelector("run", withObject:nil, afterDelay:0)
|
269
|
+
else
|
270
|
+
Bacon.context_did_finish(self)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def current_specification
|
275
|
+
@specifications[@current_specification_index]
|
276
|
+
end
|
277
|
+
|
278
|
+
def specification_did_finish(spec)
|
279
|
+
if (@current_specification_index + 1) < @specifications.size
|
280
|
+
@current_specification_index += 1
|
281
|
+
run
|
282
|
+
else
|
283
|
+
Bacon.context_did_finish(self)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
def before(&block); @before << block; end
|
288
|
+
def after(&block); @after << block; end
|
289
|
+
|
290
|
+
def behaves_like(*names)
|
291
|
+
names.each { |name| instance_eval(&Shared[name]) }
|
292
|
+
end
|
293
|
+
|
294
|
+
def it(description, &block)
|
295
|
+
return unless description =~ RestrictName
|
296
|
+
block ||= lambda { should.flunk "not implemented" }
|
297
|
+
Counter[:specifications] += 1
|
298
|
+
@specifications << Specification.new(self, description, block, @before, @after)
|
299
|
+
end
|
300
|
+
|
301
|
+
def should(*args, &block)
|
302
|
+
if Counter[:depth]==0
|
303
|
+
it('should '+args.first,&block)
|
304
|
+
else
|
305
|
+
super(*args,&block)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def describe(*args, &block)
|
310
|
+
context = Bacon::Context.new(args.join(' '), @before, @after, &block)
|
311
|
+
(parent_context = self).methods(false).each {|e|
|
312
|
+
class<<context; self end.send(:define_method, e) {|*args| parent_context.send(e, *args)}
|
313
|
+
}
|
314
|
+
context
|
315
|
+
end
|
316
|
+
|
317
|
+
def wait(seconds, &block)
|
318
|
+
current_specification.postpone_block(seconds, &block)
|
319
|
+
end
|
320
|
+
|
321
|
+
def raise?(*args, &block); block.raise?(*args); end
|
322
|
+
def throw?(*args, &block); block.throw?(*args); end
|
323
|
+
def change?(*args, &block); block.change?(*args); end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
class Object
|
329
|
+
def true?; false; end
|
330
|
+
def false?; false; end
|
331
|
+
end
|
332
|
+
|
333
|
+
class TrueClass
|
334
|
+
def true?; true; end
|
335
|
+
end
|
336
|
+
|
337
|
+
class FalseClass
|
338
|
+
def false?; true; end
|
339
|
+
end
|
340
|
+
|
341
|
+
class Proc
|
342
|
+
def raise?(*exceptions)
|
343
|
+
call
|
344
|
+
rescue *(exceptions.empty? ? RuntimeError : exceptions) => e
|
345
|
+
e
|
346
|
+
else
|
347
|
+
false
|
348
|
+
end
|
349
|
+
|
350
|
+
def throw?(sym)
|
351
|
+
catch(sym) {
|
352
|
+
call
|
353
|
+
return false
|
354
|
+
}
|
355
|
+
return true
|
356
|
+
end
|
357
|
+
|
358
|
+
def change?
|
359
|
+
pre_result = yield
|
360
|
+
called = call
|
361
|
+
post_result = yield
|
362
|
+
pre_result != post_result
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
class Numeric
|
367
|
+
def close?(to, delta)
|
368
|
+
(to.to_f - self).abs <= delta.to_f rescue false
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
class Object
|
374
|
+
def should(*args, &block) Should.new(self).be(*args, &block) end
|
375
|
+
end
|
376
|
+
|
377
|
+
module Kernel
|
378
|
+
private
|
379
|
+
def describe(*args, &block) Bacon::Context.new(args.join(' '), &block) end
|
380
|
+
def shared(name, &block) Bacon::Shared[name] = block end
|
381
|
+
end
|
382
|
+
|
383
|
+
class Should
|
384
|
+
# Kills ==, ===, =~, eql?, equal?, frozen?, instance_of?, is_a?,
|
385
|
+
# kind_of?, nil?, respond_to?, tainted?
|
386
|
+
instance_methods.each { |name| undef_method name if name =~ /\?|^\W+$/ }
|
387
|
+
|
388
|
+
def initialize(object)
|
389
|
+
@object = object
|
390
|
+
@negated = false
|
391
|
+
end
|
392
|
+
|
393
|
+
def not(*args, &block)
|
394
|
+
@negated = !@negated
|
395
|
+
|
396
|
+
if args.empty?
|
397
|
+
self
|
398
|
+
else
|
399
|
+
be(*args, &block)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def be(*args, &block)
|
404
|
+
if args.empty?
|
405
|
+
self
|
406
|
+
else
|
407
|
+
block = args.shift unless block_given?
|
408
|
+
satisfy(*args, &block)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
alias a be
|
413
|
+
alias an be
|
414
|
+
|
415
|
+
def satisfy(*args, &block)
|
416
|
+
if args.size == 1 && String === args.first
|
417
|
+
description = args.shift
|
418
|
+
else
|
419
|
+
description = ""
|
420
|
+
end
|
421
|
+
|
422
|
+
r = yield(@object, *args)
|
423
|
+
if Bacon::Counter[:depth] > 0
|
424
|
+
Bacon::Counter[:requirements] += 1
|
425
|
+
raise Bacon::Error.new(:failed, description) unless @negated ^ r
|
426
|
+
r
|
427
|
+
else
|
428
|
+
@negated ? !r : !!r
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def method_missing(name, *args, &block)
|
433
|
+
name = "#{name}?" if name.to_s =~ /\w[^?]\z/
|
434
|
+
|
435
|
+
desc = @negated ? "not " : ""
|
436
|
+
desc << @object.inspect << "." << name.to_s
|
437
|
+
desc << "(" << args.map{|x|x.inspect}.join(", ") << ") failed"
|
438
|
+
|
439
|
+
satisfy(desc) { |x| x.__send__(name, *args, &block) }
|
440
|
+
end
|
441
|
+
|
442
|
+
def equal(value) self == value end
|
443
|
+
def match(value) self =~ value end
|
444
|
+
def identical_to(value) self.equal? value end
|
445
|
+
alias same_as identical_to
|
446
|
+
|
447
|
+
def flunk(reason="Flunked")
|
448
|
+
raise Bacon::Error.new(:failed, reason)
|
449
|
+
end
|
450
|
+
end
|