peck 0.1.0
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 +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
|
+
[](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: []
|