peck 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +21 -0
- data/README.md +127 -0
- data/lib/peck/context.rb +106 -0
- data/lib/peck/counter.rb +48 -0
- data/lib/peck/debug.rb +14 -0
- data/lib/peck/delegates.rb +44 -0
- data/lib/peck/error.rb +14 -0
- data/lib/peck/expectations.rb +115 -0
- data/lib/peck/flavors/quiet.rb +8 -0
- data/lib/peck/flavors/vanilla.rb +10 -0
- data/lib/peck/notifiers/base.rb +39 -0
- data/lib/peck/notifiers/default.rb +68 -0
- data/lib/peck/specification.rb +85 -0
- data/lib/peck.rb +124 -0
- metadata +64 -0
data/COPYING
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c)
|
2
|
+
2007 - 2008 Christian Neukirchen <purl.org/net/chneukirchen>
|
3
|
+
2011 - 2012 Eloy Durán <eloy.de.enige@gmail.com>
|
4
|
+
2012 Manfred Stienstra, Fingertips <manfred@fngtps.com>
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
7
|
+
this software and associated documentation files (the "Software"), to deal in
|
8
|
+
the Software without restriction, including without limitation the rights to
|
9
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
10
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
11
|
+
subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
14
|
+
copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
18
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
19
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
20
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
21
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Peck
|
2
|
+
|
3
|
+
Peck is a concurrent spec framework.
|
4
|
+
|
5
|
+
[![Build Status](https://secure.travis-ci.org/Fingertips/Peck.png?branch=master)](http://travis-ci.org/Fingertips/Peck)
|
6
|
+
|
7
|
+
## Getting Started
|
8
|
+
|
9
|
+
You can install Peck as a gem.
|
10
|
+
|
11
|
+
$ gem install peck
|
12
|
+
|
13
|
+
Write a little test.
|
14
|
+
|
15
|
+
require 'peck/flavors/vanilla'
|
16
|
+
|
17
|
+
describe MicroMachine do
|
18
|
+
it "drives really fast" do
|
19
|
+
MicroMachine.should.drives(:fast)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
And enjoy the output.
|
24
|
+
|
25
|
+
ruby -rubygems spec/micro_machine_spec.rb -e ''
|
26
|
+
.
|
27
|
+
1 spec, 0 failures, finished in 0.0 seconds.
|
28
|
+
|
29
|
+
## Why another framework/test library/spec language?
|
30
|
+
|
31
|
+
I guess that's something you will have to find out for yourself. For us a spec
|
32
|
+
framework needs two things: a good way to describe intention of the specs and
|
33
|
+
flexibility to work with the different types of project we work on.
|
34
|
+
|
35
|
+
We really like Bacon and test/spec but we've found that they're to limiting
|
36
|
+
in some of our projects. Bacon doesn't work really well with Rails and
|
37
|
+
test/spec needs test/unit, which will be gone in Ruby 1.9.
|
38
|
+
|
39
|
+
In some projects we find that we have an enormous amount of little specs which
|
40
|
+
beg to be run concurrently.
|
41
|
+
|
42
|
+
Peck tries to be what we, and maybe you, need it to be. Within reason of
|
43
|
+
course.
|
44
|
+
|
45
|
+
### Peck can be concurrent
|
46
|
+
|
47
|
+
Peck has two run modes: serial and concurrent. Right now that's a global
|
48
|
+
setting for the whole suite.
|
49
|
+
|
50
|
+
Peck.concurrency = 9
|
51
|
+
|
52
|
+
Some projects don't allow concurrency because either the code or the test
|
53
|
+
suite aren't thread safe. We've also found that some suites actually run
|
54
|
+
slower in threads.
|
55
|
+
|
56
|
+
### Peck can host your own spec syntax
|
57
|
+
|
58
|
+
When implementing your own spec syntax you only have to add expectations
|
59
|
+
to a little accessor when they run and raise Peck::Error when something
|
60
|
+
fails.
|
61
|
+
|
62
|
+
describe "Fish" do
|
63
|
+
it "breathes with water" do
|
64
|
+
# If you don't like .should, you can write your own assertion
|
65
|
+
# DSL
|
66
|
+
expects {
|
67
|
+
Fish.breathes == water
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
With a bit of work you could make Peck run your Rspec tests.
|
73
|
+
|
74
|
+
### Peck has a pluggable notification system
|
75
|
+
|
76
|
+
You can write your own notifiers and register them with Peck's delegate
|
77
|
+
system.
|
78
|
+
|
79
|
+
class Remotifier < Peck::Notifiers::Base
|
80
|
+
def finished
|
81
|
+
HTTP.post("https://ci.lan/runs?ran=#{Peck.counter.ran}" +
|
82
|
+
"&failures=#{Peck.counter.failed}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
Remotifier.use
|
86
|
+
|
87
|
+
### Peck is extensible
|
88
|
+
|
89
|
+
Expect opening up Peck classes you can also extend during runtime with the
|
90
|
+
`once` callback. This callback is ran when a new context (describe) is
|
91
|
+
created.
|
92
|
+
|
93
|
+
Peck::Context.once do |context|
|
94
|
+
context.class_eval do
|
95
|
+
attr_accessor :controller_class
|
96
|
+
|
97
|
+
before do
|
98
|
+
@controller = controller_class.new
|
99
|
+
end
|
100
|
+
emd
|
101
|
+
end
|
102
|
+
|
103
|
+
## Documentation
|
104
|
+
|
105
|
+
Peck is still very much in flux and will probably change a lot in the coming
|
106
|
+
months. Currently we support a small number of assertions:
|
107
|
+
|
108
|
+
* should, should.not, and should.be
|
109
|
+
* should.equal
|
110
|
+
* should.raise([exception]) { }
|
111
|
+
* should.change([expression]) { }
|
112
|
+
* should.satisfy { |object| }
|
113
|
+
|
114
|
+
If you want to learn more you're probably best of reading the code
|
115
|
+
documentation.
|
116
|
+
|
117
|
+
## Copying
|
118
|
+
|
119
|
+
Peck inherits a lot of ideas, concepts and even some implementation from both
|
120
|
+
Bacon and MacBacon. Both of these projects have been released under the terms
|
121
|
+
of an MIT-style license.
|
122
|
+
|
123
|
+
Copyright (C) 2007 - 2012 Christian Neukirchen http://purl.org/net/chneukirchen
|
124
|
+
Copyright (C) 2011 - 2012 Eloy Durán eloy.de.enige@gmail.com
|
125
|
+
Copyright (C) 2012 Manfred Stienstra, Fingertips <manfred@fngtps.com>
|
126
|
+
|
127
|
+
Peck is freely distributable under the terms of an MIT-style license. See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
data/lib/peck/context.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
class Peck
|
2
|
+
class Context
|
3
|
+
attr_reader :specification
|
4
|
+
|
5
|
+
def initialize(specification)
|
6
|
+
@specification = specification
|
7
|
+
end
|
8
|
+
|
9
|
+
def describe(*args, &block)
|
10
|
+
self.class.describe(*args, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
FILENAME_WITHOUT_LINE_RE = /^(.+?):\d+/
|
15
|
+
|
16
|
+
attr_reader :description, :block, :specs, :source_file
|
17
|
+
attr_accessor :timeout
|
18
|
+
|
19
|
+
def init(before, after, *description, &block)
|
20
|
+
# Find the first file in the backtrace which is not this file
|
21
|
+
source_file = caller.find { |line| line[0,__FILE__.size] != __FILE__ }
|
22
|
+
if source_file
|
23
|
+
source_file = source_file.match(FILENAME_WITHOUT_LINE_RE)[1]
|
24
|
+
source_file = File.expand_path(source_file)
|
25
|
+
else
|
26
|
+
log("Unable to determine the file in which the context is defined.")
|
27
|
+
end
|
28
|
+
|
29
|
+
context = Class.new(self) do
|
30
|
+
@before = before.dup
|
31
|
+
@after = after.dup
|
32
|
+
@source_file = source_file
|
33
|
+
@description = description
|
34
|
+
@block = block
|
35
|
+
@specs = []
|
36
|
+
end
|
37
|
+
|
38
|
+
if @setup
|
39
|
+
@setup.each { |b| b.call(context) }
|
40
|
+
end
|
41
|
+
|
42
|
+
Peck.contexts << context
|
43
|
+
context.class_eval(&block)
|
44
|
+
context
|
45
|
+
end
|
46
|
+
|
47
|
+
def label
|
48
|
+
Peck.join_description(description)
|
49
|
+
end
|
50
|
+
|
51
|
+
def describe(*description, &block)
|
52
|
+
if Peck.context_selector.match(Peck.join_description(*description))
|
53
|
+
init(@before, @after, *description, &block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Is only ran once for every context when it's initialized. Great place
|
58
|
+
# to hook in test suite specific functionality.
|
59
|
+
#
|
60
|
+
# Peck::Context.once { |context| context.before { @name = 'Mary' } }
|
61
|
+
def once(&block)
|
62
|
+
@setup ||= []
|
63
|
+
@setup << block
|
64
|
+
end
|
65
|
+
|
66
|
+
def before(*args, &block)
|
67
|
+
add_callback(@before, *args, &block)
|
68
|
+
end
|
69
|
+
alias setup before
|
70
|
+
|
71
|
+
def after(*args, &block)
|
72
|
+
add_callback(@after, *args, &block)
|
73
|
+
end
|
74
|
+
alias teardown after
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def add_callback(chain, *args, &block)
|
79
|
+
args.each do |method|
|
80
|
+
Peck.log("Adding method `#{method}' to callback chain")
|
81
|
+
chain << Proc.new { send(method) }
|
82
|
+
end
|
83
|
+
if block_given?
|
84
|
+
chain << block
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
PECK_PART_RE = /Peck/
|
91
|
+
def self.join_description(description)
|
92
|
+
description.map do |part|
|
93
|
+
part = part.to_s
|
94
|
+
part = nil if part =~ PECK_PART_RE
|
95
|
+
part
|
96
|
+
end.compact.join(' ')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module Kernel
|
101
|
+
private
|
102
|
+
|
103
|
+
def describe(*description, &block)
|
104
|
+
Peck::Context.init([], [], *description, &block)
|
105
|
+
end
|
106
|
+
end
|
data/lib/peck/counter.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
class Peck
|
2
|
+
class << self
|
3
|
+
attr_accessor :counter
|
4
|
+
end
|
5
|
+
|
6
|
+
class Counter
|
7
|
+
def self.instance
|
8
|
+
@instance ||= new
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :ran, :passed, :failed, :pending, :missing, :events
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@ran = @passed = @failed = 0
|
15
|
+
@pending = []
|
16
|
+
@missing = []
|
17
|
+
@events = []
|
18
|
+
$stdout.sync = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def finished_specification(spec)
|
22
|
+
@ran += 1
|
23
|
+
if spec.passed?
|
24
|
+
@passed += 1
|
25
|
+
elsif spec.failed?
|
26
|
+
@failed += 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def received_pending(label)
|
31
|
+
@pending << label
|
32
|
+
end
|
33
|
+
|
34
|
+
def received_missing(spec)
|
35
|
+
@missing << spec
|
36
|
+
end
|
37
|
+
|
38
|
+
def received_exception(spec, exception)
|
39
|
+
@events << exception
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
self.counter = Counter.instance
|
44
|
+
|
45
|
+
if respond_to?(:delegates)
|
46
|
+
self.delegates << self.counter
|
47
|
+
end
|
48
|
+
end
|
data/lib/peck/debug.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
class Peck
|
4
|
+
class Delegates < Set
|
5
|
+
def self.instance
|
6
|
+
@instance ||= new
|
7
|
+
end
|
8
|
+
|
9
|
+
MESSAGES = %w(
|
10
|
+
started
|
11
|
+
finished
|
12
|
+
started_specification
|
13
|
+
finished_specification
|
14
|
+
received_missing
|
15
|
+
received_exception
|
16
|
+
).freeze
|
17
|
+
|
18
|
+
def supported_messages
|
19
|
+
MESSAGES
|
20
|
+
end
|
21
|
+
|
22
|
+
def method_missing(method, *args, &block)
|
23
|
+
method = method.to_s
|
24
|
+
if supported_messages.include?(method)
|
25
|
+
each do |delegate|
|
26
|
+
if delegate.respond_to?(method)
|
27
|
+
delegate.send(method, *args, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class << self
|
37
|
+
# This can be used by a `client' to receive status updates.
|
38
|
+
#
|
39
|
+
# Peck.delegates << Notifier.new
|
40
|
+
attr_reader :delegates
|
41
|
+
end
|
42
|
+
|
43
|
+
@delegates = Peck::Delegates.instance
|
44
|
+
end
|
data/lib/peck/error.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'peck/error'
|
2
|
+
|
3
|
+
class Peck
|
4
|
+
class Should
|
5
|
+
# Kills ==, ===, =~, eql?, equal?, frozen?, instance_of?, is_a?,
|
6
|
+
# kind_of?, nil?, respond_to?, tainted?
|
7
|
+
KILL_METHODS_RE = /\?|^\W+$/
|
8
|
+
instance_methods.each do |name|
|
9
|
+
undef_method(name) if name =~ KILL_METHODS_RE
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(this)
|
13
|
+
@this = this
|
14
|
+
@negated = false
|
15
|
+
Thread.current['peck-spec'].expectations << self
|
16
|
+
end
|
17
|
+
|
18
|
+
def not
|
19
|
+
@negated = !@negated
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def be(*args, &block)
|
24
|
+
if args.empty?
|
25
|
+
self
|
26
|
+
else
|
27
|
+
block = args.shift unless block_given?
|
28
|
+
satisfy(*args, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def satisfy(*args, &block)
|
33
|
+
if args.size == 1 && String === args.first
|
34
|
+
description = args.shift
|
35
|
+
else
|
36
|
+
description = ""
|
37
|
+
end
|
38
|
+
|
39
|
+
result = yield(@this, *args)
|
40
|
+
unless @negated ^ result
|
41
|
+
Kernel.raise Peck::Error.new(:failed, description)
|
42
|
+
end
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
def change(expression, change=nil)
|
47
|
+
if @negated
|
48
|
+
description = "#{expression} changed"
|
49
|
+
description << " by #{actual}" if change
|
50
|
+
else
|
51
|
+
description = "#{expression} didn't change"
|
52
|
+
description << " by #{change}" if change
|
53
|
+
end
|
54
|
+
|
55
|
+
satisfy(description) do |x|
|
56
|
+
difference = change || 1
|
57
|
+
binding = x.send(:binding)
|
58
|
+
|
59
|
+
before = eval(expression, binding)
|
60
|
+
result = @this.call
|
61
|
+
after = eval(expression, binding)
|
62
|
+
|
63
|
+
after == before + difference
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def raise(exception_class=nil)
|
68
|
+
exception = nil
|
69
|
+
begin
|
70
|
+
@this.call
|
71
|
+
rescue Exception => e
|
72
|
+
exception = e
|
73
|
+
end
|
74
|
+
|
75
|
+
description = if exception_class
|
76
|
+
if @negated
|
77
|
+
"expected `#{exception_class}' to not be raised"
|
78
|
+
else
|
79
|
+
"expected `#{exception_class}' to be raised, but got a `#{exception.class}'"
|
80
|
+
end
|
81
|
+
else
|
82
|
+
if @negated
|
83
|
+
"expected nothing to be raised, but got `#{exception.inspect}'"
|
84
|
+
else
|
85
|
+
"expected an exception, but nothing was raised"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
satisfy(description) do
|
90
|
+
if exception_class
|
91
|
+
exception.kind_of?(exception_class)
|
92
|
+
else
|
93
|
+
!exception.nil?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
PREDICATE_METHOD_RE = /\w[^?]\z/
|
99
|
+
def method_missing(name, *args, &block)
|
100
|
+
name = "#{name}?" if name.to_s =~ PREDICATE_METHOD_RE
|
101
|
+
|
102
|
+
desc = @negated ? "not " : ""
|
103
|
+
desc << @this.inspect << "." << name.to_s
|
104
|
+
desc << "(" << args.map{|x|x.inspect}.join(", ") << ") failed"
|
105
|
+
|
106
|
+
satisfy(desc) { |x| x.__send__(name, *args, &block) }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class Object
|
112
|
+
def should(*args, &block)
|
113
|
+
Peck::Should.new(self).be(*args, &block)
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Peck
|
2
|
+
class Notifiers
|
3
|
+
class Base
|
4
|
+
# When a file starts with this path, it's in the Peck library
|
5
|
+
PECK_PATH = File.expand_path('../../../../', __FILE__)
|
6
|
+
|
7
|
+
# Matches: `block (2 levels) in <top (required)>'
|
8
|
+
ANONYMOUS_BLOCK_RE = /`block/
|
9
|
+
|
10
|
+
def self.use
|
11
|
+
@instance ||= begin
|
12
|
+
notifier = new
|
13
|
+
notifier.install_at_exit
|
14
|
+
Peck.delegates << notifier
|
15
|
+
notifier
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def pluralize(count, stem)
|
22
|
+
count == 1 ? stem : "#{stem}s"
|
23
|
+
end
|
24
|
+
|
25
|
+
def clean_backtrace(backtrace)
|
26
|
+
stripped = []
|
27
|
+
backtrace.each do |line|
|
28
|
+
if line.start_with?(PECK_PATH) || line.start_with?("<")
|
29
|
+
elsif line =~ ANONYMOUS_BLOCK_RE
|
30
|
+
stripped << line.split(':')[0,2].join(':')
|
31
|
+
else
|
32
|
+
stripped << line
|
33
|
+
end
|
34
|
+
end
|
35
|
+
stripped.empty? ? backtrace : stripped
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'peck/notifiers/base'
|
2
|
+
|
3
|
+
class Peck
|
4
|
+
class Notifiers
|
5
|
+
class Default < Peck::Notifiers::Base
|
6
|
+
def initialize
|
7
|
+
@started_at = @finished_at = Time.now
|
8
|
+
end
|
9
|
+
|
10
|
+
def started
|
11
|
+
@started_at = Time.now
|
12
|
+
end
|
13
|
+
|
14
|
+
def finished
|
15
|
+
@finished_at = Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
def finished_specification(spec)
|
19
|
+
if spec.passed?
|
20
|
+
$stdout.write('.')
|
21
|
+
elsif spec.failed?
|
22
|
+
$stdout.write('f')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def write_exeception(number, event)
|
27
|
+
puts " #{number}) #{event.spec.label}\n\n"
|
28
|
+
backtrace = clean_backtrace(event.exception.backtrace)
|
29
|
+
puts " #{event.exception.message}\n\n\t#{backtrace.join("\n\t")}\n\n"
|
30
|
+
end
|
31
|
+
|
32
|
+
def write_event(number, event)
|
33
|
+
case event.exception
|
34
|
+
when Exception
|
35
|
+
write_exeception(number, event)
|
36
|
+
else
|
37
|
+
raise ArgumentError, "Don't know how to display event `#{event.expectation.class.name}'"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def write_events
|
42
|
+
Peck.all_events.each_with_index do |event, index|
|
43
|
+
number = index + 1
|
44
|
+
write_event(number, event)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def runtime_in_seconds
|
49
|
+
runtime = @finished_at - @started_at
|
50
|
+
(runtime * 100).round / 100.0
|
51
|
+
end
|
52
|
+
|
53
|
+
def write_stats
|
54
|
+
puts "#{Peck.counter.ran} #{pluralize(Peck.counter.ran, 'spec')}, #{Peck.counter.failed} #{pluralize(Peck.counter.failed, 'failure')}, finished in #{runtime_in_seconds} seconds."
|
55
|
+
end
|
56
|
+
|
57
|
+
def write
|
58
|
+
puts if Peck.counter.ran > 0
|
59
|
+
write_events
|
60
|
+
write_stats
|
61
|
+
end
|
62
|
+
|
63
|
+
def install_at_exit
|
64
|
+
at_exit { write }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class Peck
|
2
|
+
class Context
|
3
|
+
def self.it(description, &block)
|
4
|
+
return unless Peck.spec_selector.match(label)
|
5
|
+
specification = Specification.new(self, @before, @after, description, &block)
|
6
|
+
@specs << specification
|
7
|
+
specification
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.pending(description)
|
11
|
+
return unless Peck.spec_selector.match(label)
|
12
|
+
delegates.received_pending(description)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Event
|
17
|
+
attr_accessor :exception, :spec
|
18
|
+
|
19
|
+
def initialize(exception, spec)
|
20
|
+
@exception = exception
|
21
|
+
@spec = spec
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Specification
|
26
|
+
attr_reader :description, :context
|
27
|
+
attr_reader :expectations, :events
|
28
|
+
|
29
|
+
def initialize(context, before, after, description, &block)
|
30
|
+
@context = context.new(self)
|
31
|
+
@before = before.dup
|
32
|
+
@after = after.dup
|
33
|
+
@description = description
|
34
|
+
@block = block
|
35
|
+
|
36
|
+
@expectations = []
|
37
|
+
@events = []
|
38
|
+
end
|
39
|
+
|
40
|
+
def label
|
41
|
+
"#{@context.class.label} #{@description}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def synchronized(&block)
|
45
|
+
if semaphore = Thread.current['peck-semaphore']
|
46
|
+
semaphore.synchronize(&block)
|
47
|
+
else
|
48
|
+
block.call
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def run
|
53
|
+
if @block
|
54
|
+
@before.each { |cb| @context.instance_eval(&cb) }
|
55
|
+
begin
|
56
|
+
synchronized do
|
57
|
+
Thread.current['peck-spec'] = self
|
58
|
+
@context.instance_eval(&@block)
|
59
|
+
Thread.current['peck-spec'] = nil
|
60
|
+
end
|
61
|
+
Peck.delegates.received_missing(self) if empty?
|
62
|
+
ensure
|
63
|
+
@after.each { |cb| @context.instance_eval(&cb) }
|
64
|
+
end
|
65
|
+
else
|
66
|
+
Peck.delegates.received_missing(self)
|
67
|
+
end
|
68
|
+
rescue Object => e
|
69
|
+
Peck.delegates.received_exception(self, e)
|
70
|
+
@events << Event.new(e, self)
|
71
|
+
end
|
72
|
+
|
73
|
+
def empty?
|
74
|
+
@expectations.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
def failed?
|
78
|
+
!@events.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
def passed?
|
82
|
+
!failed? && !empty?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/peck.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
class Peck
|
2
|
+
VERSION = "0.1.0"
|
3
|
+
|
4
|
+
class << self
|
5
|
+
# Returns all the defined contexts.
|
6
|
+
attr_reader :contexts
|
7
|
+
|
8
|
+
# Used to select which contexts should be run. The match method will be
|
9
|
+
# called on these with the label of the context as argument. You can use
|
10
|
+
# a regular expression or a custom class to match what needs to be run.
|
11
|
+
#
|
12
|
+
# module ContextMatcher
|
13
|
+
# def self.match(label)
|
14
|
+
# label =~ /^Birds/
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# Peck.context_selector = ContextMatcher
|
18
|
+
attr_accessor :context_selector
|
19
|
+
|
20
|
+
# Used to select which specs should be run. See Peck.select_context
|
21
|
+
# for more information.
|
22
|
+
attr_accessor :spec_selector
|
23
|
+
|
24
|
+
# Sets the level of concurrency.
|
25
|
+
attr_accessor :concurrency
|
26
|
+
end
|
27
|
+
|
28
|
+
# A no-op unless you require 'peck/debug'
|
29
|
+
def self.log(message)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns true if the suite should run concurrent.
|
33
|
+
def self.concurrent?
|
34
|
+
concurrency && concurrency > 1
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.reset!
|
38
|
+
@contexts = []
|
39
|
+
end
|
40
|
+
|
41
|
+
@context_selector = //
|
42
|
+
@spec_selector = //
|
43
|
+
|
44
|
+
reset!
|
45
|
+
|
46
|
+
def self.all_specs
|
47
|
+
contexts.inject([]) do |all, context|
|
48
|
+
all.concat(context.specs)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.all_events
|
53
|
+
contexts.inject([]) do |all, context|
|
54
|
+
context.specs.inject(all) do |events, spec|
|
55
|
+
events.concat(spec.events)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.run
|
61
|
+
delegates.started
|
62
|
+
concurrent? ? run_concurrent : run_serial
|
63
|
+
delegates.finished
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.run_at_exit
|
67
|
+
at_exit do
|
68
|
+
run
|
69
|
+
exit Peck.counter.failed
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.run_serial
|
74
|
+
Peck.log("Running specs in serial")
|
75
|
+
Thread.current['peck-semaphore'] = Mutex.new
|
76
|
+
contexts.each do |context|
|
77
|
+
context.specs.each do |specification|
|
78
|
+
delegates.started_specification(specification)
|
79
|
+
specification.run
|
80
|
+
delegates.finished_specification(specification)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
rescue Exception => e
|
84
|
+
log("An error bubbled up from the context, this should never happen and is possibly a bug.")
|
85
|
+
raise e
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.run_concurrent
|
89
|
+
Peck.log("Running specs concurrently")
|
90
|
+
current_spec = -1
|
91
|
+
specs = all_specs
|
92
|
+
threaded do |nr|
|
93
|
+
Thread.current['peck-semaphore'] = Mutex.new
|
94
|
+
loop do
|
95
|
+
spec_index = Thread.exclusive { current_spec += 1 }
|
96
|
+
if specification = specs[spec_index]
|
97
|
+
delegates.started_specification(specification)
|
98
|
+
specification.run
|
99
|
+
delegates.finished_specification(specification)
|
100
|
+
else
|
101
|
+
break
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
delegates.finished
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.threaded
|
110
|
+
threads = []
|
111
|
+
Peck.concurrency.times do |nr|
|
112
|
+
threads[nr] = Thread.new do
|
113
|
+
yield nr
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
threads.compact.each do |thread|
|
118
|
+
begin
|
119
|
+
thread.join
|
120
|
+
rescue Interrupt
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: peck
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Manfred Stienstra
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-26 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! ' Concurrent spec framework.
|
15
|
+
|
16
|
+
'
|
17
|
+
email: manfred@fngtps.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files:
|
21
|
+
- COPYING
|
22
|
+
files:
|
23
|
+
- lib/peck/context.rb
|
24
|
+
- lib/peck/counter.rb
|
25
|
+
- lib/peck/debug.rb
|
26
|
+
- lib/peck/delegates.rb
|
27
|
+
- lib/peck/error.rb
|
28
|
+
- lib/peck/expectations.rb
|
29
|
+
- lib/peck/flavors/quiet.rb
|
30
|
+
- lib/peck/flavors/vanilla.rb
|
31
|
+
- lib/peck/notifiers/base.rb
|
32
|
+
- lib/peck/notifiers/default.rb
|
33
|
+
- lib/peck/specification.rb
|
34
|
+
- lib/peck.rb
|
35
|
+
- COPYING
|
36
|
+
- README.md
|
37
|
+
homepage:
|
38
|
+
licenses: []
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --charset=utf-8
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
requirements: []
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 1.8.11
|
59
|
+
signing_key:
|
60
|
+
specification_version: 3
|
61
|
+
summary: Peck is a concurrent spec framework which inherits a lot from the fabulous
|
62
|
+
Bacon and MacBacon. We call it a framework because it was designed to be used in
|
63
|
+
parts and is easily extended.
|
64
|
+
test_files: []
|