scampi 0.1.1
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.
- checksums.yaml +7 -0
- data/.envrc +1 -0
- data/COPYING +18 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +28 -0
- data/README.rdoc +290 -0
- data/Rakefile +80 -0
- data/bin/increment-version +45 -0
- data/bin/release-gem +32 -0
- data/bin/test +3 -0
- data/exe/scampi +27 -0
- data/flake.lock +61 -0
- data/flake.nix +43 -0
- data/lib/rubygems_plugin.rb +38 -0
- data/lib/scampi/context.rb +166 -0
- data/lib/scampi/error.rb +10 -0
- data/lib/scampi/monkey_patches.rb +61 -0
- data/lib/scampi/should.rb +67 -0
- data/lib/scampi/version.rb +5 -0
- data/lib/scampi/version.rb.erb +5 -0
- data/lib/scampi.rb +97 -0
- data/scampi.gemspec +30 -0
- data/test/spec_bacon.rb +426 -0
- data/test/spec_nontrue.rb +14 -0
- data/test/spec_should.rb +31 -0
- metadata +84 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
module Scampi
|
|
2
|
+
class Context
|
|
3
|
+
attr_reader :name, :block
|
|
4
|
+
|
|
5
|
+
def initialize(name, &block)
|
|
6
|
+
@name = name
|
|
7
|
+
@block = block
|
|
8
|
+
@items = []
|
|
9
|
+
@before = []
|
|
10
|
+
@after = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Phase 1: evaluate block to discover specs and children.
|
|
14
|
+
# Nothing is executed — it and describe just queue.
|
|
15
|
+
def register
|
|
16
|
+
tap do
|
|
17
|
+
if name =~ RestrictContext
|
|
18
|
+
instance_eval(&block)
|
|
19
|
+
else
|
|
20
|
+
self
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Count total specs recursively.
|
|
26
|
+
def count
|
|
27
|
+
@items.sum { |item|
|
|
28
|
+
case item[0]
|
|
29
|
+
when :spec then 1
|
|
30
|
+
when :child then item[1].count
|
|
31
|
+
else 0
|
|
32
|
+
end
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Phase 2: run all registered specs in order, emitting TAP subtests.
|
|
37
|
+
# +indent+ is the nesting depth (0 = inside a top-level describe).
|
|
38
|
+
# Returns true if all specs/children passed, false otherwise.
|
|
39
|
+
def execute(indent = 0)
|
|
40
|
+
prefix = " " * indent
|
|
41
|
+
inner = " " * (indent + 1)
|
|
42
|
+
|
|
43
|
+
plan = @items.count { |item| item[0] == :spec || item[0] == :child }
|
|
44
|
+
|
|
45
|
+
puts "#{prefix}# Subtest: #{@name}"
|
|
46
|
+
puts "#{inner}1..#{plan}"
|
|
47
|
+
|
|
48
|
+
befores = []
|
|
49
|
+
afters = []
|
|
50
|
+
local_n = 0
|
|
51
|
+
all_passed = true
|
|
52
|
+
|
|
53
|
+
@items.each { |item|
|
|
54
|
+
case item[0]
|
|
55
|
+
when :before
|
|
56
|
+
befores << item[1]
|
|
57
|
+
when :after
|
|
58
|
+
afters << item[1]
|
|
59
|
+
when :spec
|
|
60
|
+
Counter[:specifications] += 1
|
|
61
|
+
local_n += 1
|
|
62
|
+
passed = run_requirement(item[1], item[2], befores, afters, indent + 1, local_n)
|
|
63
|
+
all_passed = false unless passed
|
|
64
|
+
when :child
|
|
65
|
+
local_n += 1
|
|
66
|
+
child_passed = item[1].execute(indent + 1)
|
|
67
|
+
if child_passed
|
|
68
|
+
puts "#{inner}#{"ok".green} #{local_n} - #{item[1].name}"
|
|
69
|
+
else
|
|
70
|
+
puts "#{inner}#{"not ok".red} #{local_n} - #{item[1].name}"
|
|
71
|
+
end
|
|
72
|
+
all_passed = false unless child_passed
|
|
73
|
+
end
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
all_passed
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def before(&block); @items << [:before, block]; @before << block; end
|
|
80
|
+
def after(&block); @items << [:after, block]; @after << block; end
|
|
81
|
+
|
|
82
|
+
def behaves_like(*names)
|
|
83
|
+
names.each { |name| instance_eval(&Shared[name]) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def it(description, &block)
|
|
87
|
+
return unless description =~ RestrictName
|
|
88
|
+
block ||= proc { should.flunk "not implemented" }
|
|
89
|
+
@items << [:spec, description, block]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def should(*args, &block)
|
|
93
|
+
if Counter[:depth] == 0
|
|
94
|
+
it('should ' + args.first, &block)
|
|
95
|
+
else
|
|
96
|
+
super(*args, &block)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def run_requirement(description, spec, befores, afters, indent = 0, local_n = 1)
|
|
101
|
+
Scampi.handle_requirement(description, indent, local_n) do
|
|
102
|
+
begin
|
|
103
|
+
Counter[:depth] += 1
|
|
104
|
+
rescued = false
|
|
105
|
+
begin
|
|
106
|
+
befores.each { |block| instance_eval(&block) }
|
|
107
|
+
prev_req = Counter[:requirements]
|
|
108
|
+
instance_eval(&spec)
|
|
109
|
+
rescue Object => e
|
|
110
|
+
rescued = true
|
|
111
|
+
raise e
|
|
112
|
+
ensure
|
|
113
|
+
if Counter[:requirements] == prev_req and not rescued
|
|
114
|
+
raise Error.new(:missing,
|
|
115
|
+
"empty specification: #{@name} #{description}")
|
|
116
|
+
end
|
|
117
|
+
begin
|
|
118
|
+
afters.each { |block| instance_eval(&block) }
|
|
119
|
+
rescue Object => e
|
|
120
|
+
raise e unless rescued
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
rescue SystemExit, Interrupt
|
|
124
|
+
raise
|
|
125
|
+
rescue Object => e
|
|
126
|
+
ErrorLog << "#{e.class}: #{e.message}\n"
|
|
127
|
+
e.backtrace.find_all { |line| line !~ /bin\/scampi|\/scampi\.rb:\d+/ }.
|
|
128
|
+
each_with_index { |line, i|
|
|
129
|
+
ErrorLog << "\t#{line}#{i==0 ? ": #@name - #{description}" : ""}\n"
|
|
130
|
+
}
|
|
131
|
+
ErrorLog << "\n"
|
|
132
|
+
|
|
133
|
+
if e.kind_of? Error
|
|
134
|
+
Counter[e.count_as] += 1
|
|
135
|
+
e.count_as.to_s.upcase
|
|
136
|
+
else
|
|
137
|
+
Counter[:errors] += 1
|
|
138
|
+
"ERROR: #{e.class}"
|
|
139
|
+
end
|
|
140
|
+
else
|
|
141
|
+
""
|
|
142
|
+
ensure
|
|
143
|
+
Counter[:depth] -= 1
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def describe(*args, &block)
|
|
149
|
+
context = Scampi::Context.new(args.join(' '), &block)
|
|
150
|
+
(parent_context = self).methods(false).each { |e|
|
|
151
|
+
(class << context; self; end).send(:define_method, e) { |*args2, &block2|
|
|
152
|
+
parent_context.send(e, *args2, &block2)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
@before.each { |b| context.before(&b) }
|
|
156
|
+
@after.each { |b| context.after(&b) }
|
|
157
|
+
context.register
|
|
158
|
+
@items << [:child, context]
|
|
159
|
+
context
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def raise?(*args, &block) = block.raise?(*args)
|
|
163
|
+
def throw?(*args, &block) = block.throw?(*args)
|
|
164
|
+
def change?(&block) = lambda{}.change?(&block)
|
|
165
|
+
end
|
|
166
|
+
end
|
data/lib/scampi/error.rb
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
class Object
|
|
2
|
+
def true? = false
|
|
3
|
+
def false? = false
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class TrueClass
|
|
7
|
+
def true? = true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class FalseClass
|
|
11
|
+
def false? = true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Proc
|
|
15
|
+
def raise?(*exceptions)
|
|
16
|
+
call
|
|
17
|
+
rescue *(exceptions.empty? ? RuntimeError : exceptions) => e
|
|
18
|
+
e
|
|
19
|
+
else
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def throw?(sym)
|
|
24
|
+
catch(sym) {
|
|
25
|
+
call
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
return true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def change?
|
|
32
|
+
pre_result = yield
|
|
33
|
+
call
|
|
34
|
+
post_result = yield
|
|
35
|
+
pre_result != post_result
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Numeric
|
|
40
|
+
def close?(to, delta)
|
|
41
|
+
(to.to_f - self).abs <= delta.to_f rescue false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class Object
|
|
46
|
+
def should(*args, &block)
|
|
47
|
+
Scampi::Should.new(self).be(*args, &block)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module Kernel
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def describe(*args, &block)
|
|
55
|
+
Scampi.queue << Scampi::Context.new(args.join(' '), &block)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def shared(name, &block)
|
|
59
|
+
Scampi::Shared[name] = block
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Scampi
|
|
2
|
+
class Should
|
|
3
|
+
# Kills ==, ===, =~, eql?, equal?, frozen?, instance_of?, is_a?,
|
|
4
|
+
# kind_of?, nil?, respond_to?, tainted?
|
|
5
|
+
instance_methods.each { |name| undef_method name if name =~ /\?|^\W+$/ }
|
|
6
|
+
|
|
7
|
+
def initialize(object)
|
|
8
|
+
@object = object
|
|
9
|
+
@negated = false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def not(*args, &block)
|
|
13
|
+
@negated = !@negated
|
|
14
|
+
|
|
15
|
+
if args.empty?
|
|
16
|
+
self
|
|
17
|
+
else
|
|
18
|
+
be(*args, &block)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def be(*args, &block)
|
|
23
|
+
if args.empty?
|
|
24
|
+
self
|
|
25
|
+
else
|
|
26
|
+
unless block_given?
|
|
27
|
+
block = args.shift
|
|
28
|
+
end
|
|
29
|
+
satisfy(*args, &block)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
alias a be
|
|
34
|
+
alias an be
|
|
35
|
+
|
|
36
|
+
def satisfy(description="", &block)
|
|
37
|
+
r = yield(@object)
|
|
38
|
+
if Scampi::Counter[:depth] > 0
|
|
39
|
+
Scampi::Counter[:requirements] += 1
|
|
40
|
+
raise Scampi::Error.new(:failed, description) unless @negated ^ r
|
|
41
|
+
r
|
|
42
|
+
else
|
|
43
|
+
@negated ? !r : !!r
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def method_missing(name, *args, &block)
|
|
48
|
+
name = "#{name}?" if name.to_s =~ /\w[^?]\z/
|
|
49
|
+
|
|
50
|
+
desc = @negated ? "not ".dup : "".dup
|
|
51
|
+
desc << @object.inspect << "." << name.to_s
|
|
52
|
+
desc << "(" << args.map{|x|x.inspect}.join(", ") << ") failed"
|
|
53
|
+
|
|
54
|
+
satisfy(desc) { |x| x.__send__(name, *args, &block) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def equal(value) = self == value
|
|
58
|
+
def match(value) = self =~ value
|
|
59
|
+
|
|
60
|
+
def identical_to(value) = self.equal? value
|
|
61
|
+
alias same_as identical_to
|
|
62
|
+
|
|
63
|
+
def flunk(reason="Flunked")
|
|
64
|
+
raise Scampi::Error.new(:failed, reason)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
data/lib/scampi.rb
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Scampi -- small RSpec clone with TAP output.
|
|
2
|
+
#
|
|
3
|
+
# Forked from Bacon by Christian Neukirchen.
|
|
4
|
+
# "Truth will sooner come out from error than from confusion." ---Francis Bacon
|
|
5
|
+
|
|
6
|
+
# Copyright (C) 2007, 2008, 2012 Christian Neukirchen <purl.org/net/chneukirchen>
|
|
7
|
+
#
|
|
8
|
+
# Scampi is freely distributable under the terms of an MIT-style license.
|
|
9
|
+
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
10
|
+
|
|
11
|
+
require_relative 'scampi/version'
|
|
12
|
+
require 'colorize_extended'
|
|
13
|
+
|
|
14
|
+
module Scampi
|
|
15
|
+
Counter = Hash.new(0)
|
|
16
|
+
ErrorLog = "".dup
|
|
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
|
+
@queue = []
|
|
27
|
+
@ran = false
|
|
28
|
+
|
|
29
|
+
def self.queue
|
|
30
|
+
@queue
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.run
|
|
34
|
+
return if @ran
|
|
35
|
+
@ran = true
|
|
36
|
+
|
|
37
|
+
# Register: evaluate all describe blocks to discover specs
|
|
38
|
+
@queue.each(&:register)
|
|
39
|
+
|
|
40
|
+
# TAP version + plan (one entry per top-level describe)
|
|
41
|
+
puts "TAP version 14"
|
|
42
|
+
puts "1..#{@queue.size}"
|
|
43
|
+
|
|
44
|
+
# Execute all specs as subtests
|
|
45
|
+
@queue.each_with_index do |context, i|
|
|
46
|
+
passed = context.execute(0)
|
|
47
|
+
n = i + 1
|
|
48
|
+
if passed
|
|
49
|
+
puts "#{"ok".green} #{n} - #{context.name}"
|
|
50
|
+
else
|
|
51
|
+
puts "#{"not ok".red} #{n} - #{context.name}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Summary comment
|
|
56
|
+
tests, assertions, failures, errors =
|
|
57
|
+
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
|
58
|
+
puts "# #{tests} tests, #{assertions} assertions, #{failures} failures, #{errors} errors"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.summary_on_exit
|
|
62
|
+
return if Counter[:installed_summary] > 0
|
|
63
|
+
@timer = Time.now
|
|
64
|
+
at_exit {
|
|
65
|
+
run
|
|
66
|
+
if $!
|
|
67
|
+
raise $!
|
|
68
|
+
elsif Counter[:errors] + Counter[:failed] > 0
|
|
69
|
+
exit 1
|
|
70
|
+
end
|
|
71
|
+
}
|
|
72
|
+
Counter[:installed_summary] += 1
|
|
73
|
+
end
|
|
74
|
+
class << self; alias summary_at_exit summary_on_exit; end
|
|
75
|
+
|
|
76
|
+
# TAP output
|
|
77
|
+
|
|
78
|
+
def self.handle_requirement(description, indent = 0, local_n = 1)
|
|
79
|
+
ErrorLog.replace ""
|
|
80
|
+
error = yield
|
|
81
|
+
prefix = " " * indent
|
|
82
|
+
if error.empty?
|
|
83
|
+
puts "#{prefix}#{"ok".green} #{local_n} - #{description}"
|
|
84
|
+
true
|
|
85
|
+
else
|
|
86
|
+
puts "#{prefix}#{"not ok".red} #{local_n} - #{description}: #{error}"
|
|
87
|
+
puts ErrorLog.strip.gsub(/^/, "#{prefix}# ") if Backtraces
|
|
88
|
+
false
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
require_relative 'scampi/error'
|
|
94
|
+
require_relative 'scampi/context'
|
|
95
|
+
require_relative 'scampi/should'
|
|
96
|
+
require_relative 'scampi/monkey_patches'
|
|
97
|
+
require_relative 'rubygems_plugin'
|
data/scampi.gemspec
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require_relative 'lib/scampi/version'
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = "scampi"
|
|
5
|
+
s.version = Scampi::VERSION
|
|
6
|
+
s.platform = Gem::Platform::RUBY
|
|
7
|
+
s.summary = "a small RSpec clone with built-in TAP support"
|
|
8
|
+
s.license = "MIT"
|
|
9
|
+
|
|
10
|
+
s.description = <<-EOF
|
|
11
|
+
Scampi is a small RSpec clone weighing less than 350 LoC but
|
|
12
|
+
nevertheless providing all essential features. Includes a
|
|
13
|
+
TAP (Test Anything Protocol) harness and assertion library.
|
|
14
|
+
|
|
15
|
+
http://github.com/general-intelligence-systems/scampi
|
|
16
|
+
EOF
|
|
17
|
+
|
|
18
|
+
s.files = `git ls-files`.split("\n") - [".gitignore"]
|
|
19
|
+
s.bindir = 'exe'
|
|
20
|
+
s.executables = ['scampi']
|
|
21
|
+
s.require_path = 'lib'
|
|
22
|
+
s.extra_rdoc_files = ['README.rdoc']
|
|
23
|
+
s.test_files = []
|
|
24
|
+
|
|
25
|
+
s.add_dependency 'colorize-extended'
|
|
26
|
+
|
|
27
|
+
s.author = 'Nathan K'
|
|
28
|
+
s.email = 'nathankidd@hey.com'
|
|
29
|
+
s.homepage = 'http://github.com/general-intelligence-systems/scampi'
|
|
30
|
+
end
|