riot 0.12.1 → 0.12.2
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/.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'
|