riot 0.12.1 → 0.12.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/.yardopts +6 -0
- data/CHANGELOG +58 -46
- data/Gemfile +4 -0
- data/README.markdown +322 -85
- data/Rakefile +3 -38
- data/lib/riot.rb +74 -11
- data/lib/riot/assertion.rb +32 -1
- data/lib/riot/assertion_macro.rb +57 -10
- data/lib/riot/assertion_macros/any.rb +4 -2
- data/lib/riot/assertion_macros/assigns.rb +18 -4
- data/lib/riot/assertion_macros/empty.rb +2 -0
- data/lib/riot/assertion_macros/equals.rb +4 -0
- data/lib/riot/assertion_macros/equivalent_to.rb +5 -1
- data/lib/riot/assertion_macros/exists.rb +4 -2
- data/lib/riot/assertion_macros/includes.rb +5 -1
- data/lib/riot/assertion_macros/kind_of.rb +5 -1
- data/lib/riot/assertion_macros/matches.rb +5 -1
- data/lib/riot/assertion_macros/nil.rb +3 -1
- data/lib/riot/assertion_macros/not_borat.rb +6 -0
- data/lib/riot/assertion_macros/raises.rb +13 -7
- data/lib/riot/assertion_macros/respond_to.rb +5 -1
- data/lib/riot/assertion_macros/same_elements.rb +6 -2
- data/lib/riot/assertion_macros/size.rb +5 -1
- data/lib/riot/context.rb +58 -10
- data/lib/riot/context_helpers.rb +20 -4
- data/lib/riot/context_options.rb +14 -4
- data/lib/riot/message.rb +87 -6
- data/lib/riot/middleware.rb +69 -4
- data/lib/riot/reporter.rb +71 -110
- data/lib/riot/reporter/dot_matrix.rb +49 -0
- data/lib/riot/reporter/io.rb +85 -0
- data/lib/riot/reporter/pretty_dot_matrix.rb +38 -0
- data/lib/riot/reporter/silent.rb +18 -0
- data/lib/riot/reporter/story.rb +52 -0
- data/lib/riot/rr.rb +28 -4
- data/lib/riot/runnable.rb +53 -0
- data/lib/riot/situation.rb +45 -0
- data/lib/riot/version.rb +4 -0
- data/riot.gemspec +14 -155
- data/test/core/assertion_macros/any_test.rb +10 -10
- data/test/core/assertion_macros/assigns_test.rb +7 -7
- data/test/core/assertion_macros/equivalent_to_test.rb +3 -3
- data/test/core/assertion_macros/exists_test.rb +4 -4
- data/test/core/assertion_macros/includes_test.rb +2 -2
- data/test/core/assertion_macros/kind_of_test.rb +3 -3
- data/test/core/assertion_macros/matches_test.rb +2 -2
- data/test/core/assertion_macros/nil_test.rb +2 -2
- data/test/core/assertion_macros/raises_test.rb +10 -10
- data/test/core/assertion_macros/respond_to_test.rb +2 -2
- data/test/core/assertion_macros/same_elements_test.rb +4 -4
- data/test/core/assertion_macros/size_test.rb +6 -6
- data/test/core/context/asserts_with_arguments_test.rb +12 -0
- data/test/core/context/using_describe_in_a_test.rb +1 -1
- data/test/core/report_test.rb +9 -5
- data/test/core/runnable/message_test.rb +10 -6
- data/test/teststrap.rb +0 -6
- metadata +20 -33
- data/TODO.markdown +0 -14
- data/VERSION +0 -1
- data/test.watchr +0 -70
- data/test/benchmark/colorize.rb +0 -39
data/lib/riot/context_options.rb
CHANGED
@@ -7,18 +7,28 @@ module Riot
|
|
7
7
|
# context "Foo" do
|
8
8
|
# set :transactional, true
|
9
9
|
# end
|
10
|
-
|
10
|
+
#
|
11
|
+
# @param [Object] key the key used to look up the option value later
|
12
|
+
# @param [Object] value the option value to store
|
13
|
+
def set(key, value)
|
14
|
+
options[key] = value
|
15
|
+
end
|
11
16
|
|
12
17
|
# Returns the value of a set option. The key must match exactly, symbols and strings are not
|
13
18
|
# interchangeable.
|
14
19
|
#
|
20
|
+
# @param [Object] key the key used to look up the option value
|
15
21
|
# @return [Object]
|
16
|
-
def option(key)
|
22
|
+
def option(key)
|
23
|
+
options[key]
|
24
|
+
end
|
17
25
|
|
18
|
-
# Returns the
|
26
|
+
# Returns the hash of defined options.
|
19
27
|
#
|
20
28
|
# @return [Hash]
|
21
|
-
def options
|
29
|
+
def options
|
30
|
+
@options ||= {}
|
31
|
+
end
|
22
32
|
|
23
33
|
end # ContextOptions
|
24
34
|
end # Riot
|
data/lib/riot/message.rb
CHANGED
@@ -3,21 +3,102 @@ class BlankSlate
|
|
3
3
|
end
|
4
4
|
|
5
5
|
module Riot
|
6
|
+
# A Message is similar in nature (but not implementation) to a string buffer; you put some strings in and
|
7
|
+
# calling {#to_s} will generate a single new string. What's special abnout Message is how you get strings
|
8
|
+
# into it. By convention, any method called on a Message that isn't defined will have its name turned into
|
9
|
+
# a string and any underscores replaced with spaces. This happens for each method call and those small
|
10
|
+
# messages are chained together at the end. For instance:
|
11
|
+
#
|
12
|
+
# message = Riot::Message.new
|
13
|
+
# message.hello_world.to_s
|
14
|
+
# => "hello world"
|
15
|
+
#
|
16
|
+
# message.whats_the_news.to_s
|
17
|
+
# => "hello world whats the news"
|
18
|
+
#
|
19
|
+
# For every method called it is also acceptable to pass any number of arguments. These arguments will be
|
20
|
+
# added to the final message after having {Kernel#inspect} called on them. Another for instance:
|
21
|
+
#
|
22
|
+
# message = Riot::Message.new
|
23
|
+
# message.expected([1, 2, 3], "foo").not([3, 2, 1], "bar")
|
24
|
+
# message.to_s
|
25
|
+
# => 'expected [1, 2, 3], "foo", not [3, 2, 1], "bar"'
|
26
|
+
#
|
27
|
+
# This is useful for - and was originally intended for - generating pass/fail messages from
|
28
|
+
# {Riot::AssertionMacro assertion macros}.
|
6
29
|
class Message < BlankSlate
|
30
|
+
|
31
|
+
# Creates a new Message instance.
|
32
|
+
#
|
33
|
+
# @param [Array<Object>] *phrases an array of objects to be inspected
|
7
34
|
def initialize(*phrases)
|
8
|
-
@chunks = []
|
35
|
+
@chunks = []
|
36
|
+
_inspect(phrases)
|
9
37
|
end
|
10
38
|
|
39
|
+
# Generates the string value of the built-up message.
|
40
|
+
#
|
41
|
+
# @return [String]
|
11
42
|
def to_s; @chunks.join.strip; end
|
43
|
+
alias_method :inspect, :to_s
|
44
|
+
|
45
|
+
# Converts any method call into a more readable string by replacing underscores with spaces. Any
|
46
|
+
# arguments to the method are inspected and appended to the final message. Blocks are currently ignored.
|
47
|
+
#
|
48
|
+
# @param [String, Symbol] meth the method name to be converted into a more readable form
|
49
|
+
# @param [Array<Object>] *phrases an array of objects to be inspected
|
50
|
+
# @return [Riot::Message] this instance for use in chaining calls
|
51
|
+
def method_missing(meth, *phrases, &block)
|
52
|
+
push(meth.to_s.gsub('_', ' '))
|
53
|
+
_inspect(phrases)
|
54
|
+
end
|
12
55
|
|
13
|
-
|
56
|
+
# Adds a comma then the provided phrase to this message.
|
57
|
+
#
|
58
|
+
# Riot::Message.new.hello.comma("world").to_s
|
59
|
+
# => "hello, world"
|
60
|
+
#
|
61
|
+
# @param [String] str any string phrase to be added after the comma
|
62
|
+
# @param [Array<Object>] *phrases an array of objects to be inspected
|
63
|
+
# @return [Riot::Message] this instance for use in chaining calls
|
64
|
+
def comma(str, *phrases)
|
65
|
+
_concat([", ", str])
|
66
|
+
_inspect(phrases)
|
67
|
+
end
|
14
68
|
|
15
|
-
|
69
|
+
# Adds the string ", but".
|
70
|
+
#
|
71
|
+
# Riot::Message.new.any_number.but(52).to_s
|
72
|
+
# => "any number, but 52"
|
73
|
+
#
|
74
|
+
# @param [Array<Object>] *phrases an array of objects to be inspected
|
75
|
+
# @return [Riot::Message] this instance for use in chaining calls
|
16
76
|
def but(*phrases); comma("but", *phrases); end
|
77
|
+
|
78
|
+
# Adds the string ", not".
|
79
|
+
#
|
80
|
+
# Riot::Message.new.expected_freebies.not("$1.50").to_s
|
81
|
+
# => 'expected freebies, not "$1.50"'
|
82
|
+
#
|
83
|
+
# @param [Array<Object>] *phrases an array of objects to be inspected
|
84
|
+
# @return [Riot::Message] this instance for use in chaining calls
|
17
85
|
def not(*phrases); comma("not", *phrases); end
|
18
|
-
|
86
|
+
|
19
87
|
private
|
20
|
-
def
|
21
|
-
|
88
|
+
def push(str)
|
89
|
+
_concat([" ", str])
|
90
|
+
end
|
91
|
+
|
92
|
+
def _concat(chunks)
|
93
|
+
@chunks.concat(chunks)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def _inspect(phrases)
|
98
|
+
unless phrases.empty?
|
99
|
+
push(phrases.map { |phrase| phrase.inspect }.join(", "))
|
100
|
+
end
|
101
|
+
self
|
102
|
+
end
|
22
103
|
end # Message
|
23
104
|
end # Riot
|
data/lib/riot/middleware.rb
CHANGED
@@ -1,5 +1,60 @@
|
|
1
1
|
module Riot
|
2
2
|
|
3
|
+
# Context middlewares are chainable, context preparers. This to say that a middleware knows about a single
|
4
|
+
# neighbor and that it can prepare context before the context is "run". As a for instance, suppose you
|
5
|
+
# wanted the following to be possible.
|
6
|
+
#
|
7
|
+
# context Person do
|
8
|
+
# denies(:valid?)
|
9
|
+
# end # Person
|
10
|
+
#
|
11
|
+
# Without writing a middleware, the topic in this would actually be nil, but what the context is saying is
|
12
|
+
# that there should be something in the topic that responds to +:valid?+; an instance of +Person+ in this
|
13
|
+
# case. We can do this with middleware like so:
|
14
|
+
#
|
15
|
+
# class Modelware < Riot::ContextMiddleware
|
16
|
+
# register
|
17
|
+
#
|
18
|
+
# def call(context)
|
19
|
+
# if context.description.kind_of?(Model)
|
20
|
+
# context.setup { context.description.new }
|
21
|
+
# end
|
22
|
+
# middleware.call(context)
|
23
|
+
# end
|
24
|
+
# end # Modelware
|
25
|
+
#
|
26
|
+
# That's good stuff. If you're familiar at all with the nature of Rack middleware - how to implement it,
|
27
|
+
# how it's executed, etc. - you'll be familiar with Context middleware as the principles are similar:
|
28
|
+
#
|
29
|
+
# 1. Define a class that extends {Riot::ContextMiddleware}
|
30
|
+
# 2. Call +register+
|
31
|
+
# 3. Implement a +call+ method that accepts the Context that is about to be executed
|
32
|
+
# 4. Do stuff, but make sure to pass the call along with +middleware.call(context)+
|
33
|
+
#
|
34
|
+
# Steps 1, 2, and 3 should be pretty straight-forward. Currently, +context+ is the only argument to +call+.
|
35
|
+
# When your middleware is initialized it is given the next registered middleware in the chain (which is
|
36
|
+
# where the `middleware` method gets its value from).
|
37
|
+
#
|
38
|
+
# So, "Do stuff" from step 4 is the where we start breaking things down. What can you actually do? Well,
|
39
|
+
# you can do anything to the context that you could do if you were writing a Riot test; and I do mean
|
40
|
+
# anything.
|
41
|
+
#
|
42
|
+
# * Add setup blocks (as many as you like)
|
43
|
+
# * Add teardown blocks (as many as you like)
|
44
|
+
# * Add hookup blocks (as many as you like)
|
45
|
+
# * Add helpers (as many as you like)
|
46
|
+
# * Add assertions
|
47
|
+
#
|
48
|
+
# The context in question will not run before all middleware have been applied to the context; this is
|
49
|
+
# different behavior than that of Rack middleware. {Riot::ContextMiddleware} is only about preparing a
|
50
|
+
# context, not about executing it. Thus, where in your method you actually pass the call off to the next
|
51
|
+
# middleware in the chain has impact on how the context is set up. Basically, whatever you do before
|
52
|
+
# calling `middleware.call(context)` is done before any other middleware gets setup and before the innards
|
53
|
+
# of the context itself are applied. Whatever you do after that call is done after all that, but still
|
54
|
+
# before the actual setups, hookups, assertions, and teardowns are run.
|
55
|
+
#
|
56
|
+
# Do not expect the same instance of middleware to exist from one {Riot::Context} instance to the next. It
|
57
|
+
# is highly likely that each {Riot::Context} will instantiate their own middleware instances.
|
3
58
|
class ContextMiddleware
|
4
59
|
# Registers the current middleware class with Riot so that it may be included in the set of middlewares
|
5
60
|
# Riot will poke before executing a Context.
|
@@ -12,16 +67,25 @@ module Riot
|
|
12
67
|
# context.hookup { ... }
|
13
68
|
# end
|
14
69
|
# end
|
15
|
-
def self.register
|
70
|
+
def self.register
|
71
|
+
Context.middlewares << self
|
72
|
+
end
|
16
73
|
|
17
|
-
|
74
|
+
# Theoretically, the next middleware in the stack
|
75
|
+
attr_reader :middleware
|
18
76
|
|
77
|
+
# Create a new middleware instance and give it the next middleware in the chain.
|
78
|
+
#
|
79
|
+
# @param [Riot::ContextMiddleware] middleware the next middleware instance in the chain
|
19
80
|
def initialize(middleware)
|
20
81
|
@middleware = middleware
|
21
82
|
end
|
22
83
|
|
23
|
-
# The
|
24
|
-
#
|
84
|
+
# The magic happens here. Because you have access to the Context, you can add your own setups, hookups,
|
85
|
+
# etc. +call+ will be called before any tests are run, but after the Context is configured. Though
|
86
|
+
# something will likely be returned, do not put any faith in what that will be.
|
87
|
+
#
|
88
|
+
# @param [Riot::Context] context the Context instance that will be prepared by registered middleware
|
25
89
|
def call(context)
|
26
90
|
raise "You should implement call yourself"
|
27
91
|
end
|
@@ -34,6 +98,7 @@ module Riot
|
|
34
98
|
@context_definition = context_definition
|
35
99
|
end
|
36
100
|
|
101
|
+
# (see Riot::ContextMiddleware#call)
|
37
102
|
def call(context) context.instance_eval(&@context_definition); end
|
38
103
|
end # AllImportantMiddleware
|
39
104
|
|
data/lib/riot/reporter.rb
CHANGED
@@ -1,7 +1,27 @@
|
|
1
1
|
module Riot
|
2
|
+
|
3
|
+
# A Reporter decides how to output the result of a test. When a context is set to be executed, the
|
4
|
+
# {Riot::Reporter#describe_context} method is called with the context that will be running; this remains
|
5
|
+
# so until the next context is executed. After each {Riot#Assertion#evaluate assertion is evaluated},
|
6
|
+
# {Riot::Reporter#report} is called with the description of the assertion and the resulting response.
|
7
|
+
#
|
8
|
+
# The general idea is that a sub-class of Reporter should be defined that knows specifically how to
|
9
|
+
# output the reported results. In the sub-class, you simply need to implement a +pass+, +fail+, +error+,
|
10
|
+
# and +results+ method.
|
2
11
|
class Reporter
|
3
|
-
|
12
|
+
# Count of successful assertions so far
|
13
|
+
attr_accessor :passes
|
14
|
+
|
15
|
+
# Count of failed assertions so far
|
16
|
+
attr_accessor :failures
|
17
|
+
|
18
|
+
# Count of errored assertions so far
|
19
|
+
attr_accessor :errors
|
4
20
|
|
21
|
+
# The context that is currently being reported on
|
22
|
+
attr_accessor :current_context
|
23
|
+
|
24
|
+
# Creates a new Reporter instance and initializes counts to zero
|
5
25
|
def initialize
|
6
26
|
@passes = @failures = @errors = 0
|
7
27
|
@current_context = ""
|
@@ -9,8 +29,17 @@ module Riot
|
|
9
29
|
|
10
30
|
def new(*args, &block); self; end
|
11
31
|
|
12
|
-
|
32
|
+
# Returns true if no failures or errors have been produced yet.
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
def success?
|
36
|
+
(@failures + @errors) == 0
|
37
|
+
end
|
13
38
|
|
39
|
+
# Starts a timer, execute the provided block, then reports the results. Useful for timing context
|
40
|
+
# execution(s).
|
41
|
+
#
|
42
|
+
# @param [lambda] &block the contexts to run
|
14
43
|
def summarize(&block)
|
15
44
|
started = Time.now
|
16
45
|
yield
|
@@ -18,8 +47,18 @@ module Riot
|
|
18
47
|
results(Time.now - started)
|
19
48
|
end
|
20
49
|
|
21
|
-
|
50
|
+
# Called when a new context is about to execute to set the state for this Reporter instance.
|
51
|
+
#
|
52
|
+
# @param [Riot::Context] context the context that is about to execute
|
53
|
+
def describe_context(context)
|
54
|
+
@current_context = context
|
55
|
+
end
|
22
56
|
|
57
|
+
# Called immediately after an assertion has been evaluated. From this method either +pass+, +fail+,
|
58
|
+
# or +error+ will be called.
|
59
|
+
#
|
60
|
+
# @param [String] description the description of the assertion
|
61
|
+
# @param [Array<Symbol, *[Object]>] response the evaluation response from the assertion
|
23
62
|
def report(description, response)
|
24
63
|
code, result = *response
|
25
64
|
case code
|
@@ -36,120 +75,42 @@ module Riot
|
|
36
75
|
end
|
37
76
|
end
|
38
77
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
def initialize(writer=STDOUT)
|
46
|
-
super()
|
47
|
-
@writer = writer
|
48
|
-
end
|
49
|
-
def puts(message) @writer.puts(message); end
|
50
|
-
def print(message) @writer.print(message); end
|
51
|
-
|
52
|
-
def line_info(line, file)
|
53
|
-
line ? "(on line #{line} in #{file})" : ""
|
54
|
-
end
|
55
|
-
|
56
|
-
def results(time_taken)
|
57
|
-
values = [passes, failures, errors, ("%0.6f" % time_taken)]
|
58
|
-
puts "\n%d passes, %d failures, %d errors in %s seconds" % values
|
59
|
-
end
|
60
|
-
|
61
|
-
def format_error(e)
|
62
|
-
format = []
|
63
|
-
format << " #{e.class.name} occurred"
|
64
|
-
format << "#{e.to_s}"
|
65
|
-
filter_backtrace(e.backtrace) { |line| format << " at #{line}" }
|
66
|
-
|
67
|
-
format.join("\n")
|
68
|
-
end
|
69
|
-
|
70
|
-
protected
|
71
|
-
def filter_backtrace(backtrace)
|
72
|
-
cleansed = []
|
73
|
-
bad = true
|
74
|
-
|
75
|
-
# goal is to filter all the riot stuff/rake before the first non riot thing
|
76
|
-
backtrace.reverse_each do |bt|
|
77
|
-
# make sure we are still in the bad part
|
78
|
-
bad = (bt =~ /\/lib\/riot/ || bt =~ /rake_test_loader/) if bad
|
79
|
-
|
80
|
-
yield bt unless bad
|
81
|
-
end
|
82
|
-
|
83
|
-
cleansed.empty?? backtrace : cleansed
|
84
|
-
end
|
85
|
-
|
86
|
-
begin
|
87
|
-
raise LoadError if ENV["TM_MODE"]
|
88
|
-
require 'rubygems'
|
89
|
-
require 'term/ansicolor'
|
90
|
-
include Term::ANSIColor
|
91
|
-
rescue LoadError
|
92
|
-
def green(str); str; end
|
93
|
-
alias :red :green
|
94
|
-
alias :yellow :green
|
78
|
+
# Called if the assertion passed.
|
79
|
+
#
|
80
|
+
# @param [String] description the description of the assertion
|
81
|
+
# @param [Array<Symbol, String]>] result the evaluation response from the assertion
|
82
|
+
def pass(description, result)
|
83
|
+
raise "Implement this in a sub-class"
|
95
84
|
end
|
96
|
-
end
|
97
|
-
|
98
|
-
class StoryReporter < IOReporter
|
99
|
-
def describe_context(context)
|
100
|
-
super
|
101
|
-
puts context.detailed_description
|
102
|
-
end
|
103
|
-
def pass(description, message) puts " + " + green("#{description} #{message}".strip); end
|
104
85
|
|
86
|
+
# Called if the assertion failed.
|
87
|
+
#
|
88
|
+
# @param [String] description the description of the assertion
|
89
|
+
# @param [Array<Symbol, String, Number, String]>] response the evaluation response from the assertion
|
105
90
|
def fail(description, message, line, file)
|
106
|
-
|
91
|
+
raise "Implement this in a sub-class"
|
107
92
|
end
|
108
93
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
def error(description,
|
114
|
-
|
115
|
-
puts red(format_error(e))
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
class DotMatrixReporter < IOReporter
|
120
|
-
def initialize(writer=STDOUT)
|
121
|
-
super
|
122
|
-
@details = []
|
123
|
-
end
|
124
|
-
|
125
|
-
def pass(description, message)
|
126
|
-
print green(".")
|
127
|
-
end
|
128
|
-
|
129
|
-
def fail(description, message, line, file)
|
130
|
-
print yellow("F")
|
131
|
-
@details << "FAILURE - #{test_detail(description, message)} #{line_info(line, file)}".strip
|
132
|
-
end
|
133
|
-
|
134
|
-
def error(description, e)
|
135
|
-
print red("E")
|
136
|
-
@details << "ERROR - #{test_detail(description, format_error(e))}"
|
94
|
+
# Called if the assertion had an unexpected error.
|
95
|
+
#
|
96
|
+
# @param [String] description the description of the assertion
|
97
|
+
# @param [Array<Symbol, Exception]>] result the exception from the assertion
|
98
|
+
def error(description, result)
|
99
|
+
raise "Implement this in a sub-class"
|
137
100
|
end
|
138
101
|
|
102
|
+
# Called after all contexts have finished. This is where the final results can be output.
|
103
|
+
#
|
104
|
+
# @param [Number] time_taken number of seconds taken to run everything
|
139
105
|
def results(time_taken)
|
140
|
-
|
141
|
-
super
|
142
|
-
end
|
143
|
-
private
|
144
|
-
def test_detail(description, message)
|
145
|
-
"#{current_context.detailed_description} #{description} => #{message}"
|
106
|
+
raise "Implement this in a sub-class"
|
146
107
|
end
|
147
|
-
end
|
148
|
-
|
149
|
-
class SilentReporter < Reporter
|
150
|
-
def pass(description, message); end
|
151
|
-
def fail(description, message, line, file); end
|
152
|
-
def error(description, e); end
|
153
|
-
def results(time_taken); end
|
154
|
-
end
|
108
|
+
end # Reporter
|
109
|
+
|
155
110
|
end # Riot
|
111
|
+
|
112
|
+
require 'riot/reporter/silent'
|
113
|
+
require 'riot/reporter/io'
|
114
|
+
require 'riot/reporter/story'
|
115
|
+
require 'riot/reporter/dot_matrix'
|
116
|
+
require 'riot/reporter/pretty_dot_matrix'
|