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.
Files changed (62) hide show
  1. data/.gitignore +10 -0
  2. data/.yardopts +6 -0
  3. data/CHANGELOG +58 -46
  4. data/Gemfile +4 -0
  5. data/README.markdown +322 -85
  6. data/Rakefile +3 -38
  7. data/lib/riot.rb +74 -11
  8. data/lib/riot/assertion.rb +32 -1
  9. data/lib/riot/assertion_macro.rb +57 -10
  10. data/lib/riot/assertion_macros/any.rb +4 -2
  11. data/lib/riot/assertion_macros/assigns.rb +18 -4
  12. data/lib/riot/assertion_macros/empty.rb +2 -0
  13. data/lib/riot/assertion_macros/equals.rb +4 -0
  14. data/lib/riot/assertion_macros/equivalent_to.rb +5 -1
  15. data/lib/riot/assertion_macros/exists.rb +4 -2
  16. data/lib/riot/assertion_macros/includes.rb +5 -1
  17. data/lib/riot/assertion_macros/kind_of.rb +5 -1
  18. data/lib/riot/assertion_macros/matches.rb +5 -1
  19. data/lib/riot/assertion_macros/nil.rb +3 -1
  20. data/lib/riot/assertion_macros/not_borat.rb +6 -0
  21. data/lib/riot/assertion_macros/raises.rb +13 -7
  22. data/lib/riot/assertion_macros/respond_to.rb +5 -1
  23. data/lib/riot/assertion_macros/same_elements.rb +6 -2
  24. data/lib/riot/assertion_macros/size.rb +5 -1
  25. data/lib/riot/context.rb +58 -10
  26. data/lib/riot/context_helpers.rb +20 -4
  27. data/lib/riot/context_options.rb +14 -4
  28. data/lib/riot/message.rb +87 -6
  29. data/lib/riot/middleware.rb +69 -4
  30. data/lib/riot/reporter.rb +71 -110
  31. data/lib/riot/reporter/dot_matrix.rb +49 -0
  32. data/lib/riot/reporter/io.rb +85 -0
  33. data/lib/riot/reporter/pretty_dot_matrix.rb +38 -0
  34. data/lib/riot/reporter/silent.rb +18 -0
  35. data/lib/riot/reporter/story.rb +52 -0
  36. data/lib/riot/rr.rb +28 -4
  37. data/lib/riot/runnable.rb +53 -0
  38. data/lib/riot/situation.rb +45 -0
  39. data/lib/riot/version.rb +4 -0
  40. data/riot.gemspec +14 -155
  41. data/test/core/assertion_macros/any_test.rb +10 -10
  42. data/test/core/assertion_macros/assigns_test.rb +7 -7
  43. data/test/core/assertion_macros/equivalent_to_test.rb +3 -3
  44. data/test/core/assertion_macros/exists_test.rb +4 -4
  45. data/test/core/assertion_macros/includes_test.rb +2 -2
  46. data/test/core/assertion_macros/kind_of_test.rb +3 -3
  47. data/test/core/assertion_macros/matches_test.rb +2 -2
  48. data/test/core/assertion_macros/nil_test.rb +2 -2
  49. data/test/core/assertion_macros/raises_test.rb +10 -10
  50. data/test/core/assertion_macros/respond_to_test.rb +2 -2
  51. data/test/core/assertion_macros/same_elements_test.rb +4 -4
  52. data/test/core/assertion_macros/size_test.rb +6 -6
  53. data/test/core/context/asserts_with_arguments_test.rb +12 -0
  54. data/test/core/context/using_describe_in_a_test.rb +1 -1
  55. data/test/core/report_test.rb +9 -5
  56. data/test/core/runnable/message_test.rb +10 -6
  57. data/test/teststrap.rb +0 -6
  58. metadata +20 -33
  59. data/TODO.markdown +0 -14
  60. data/VERSION +0 -1
  61. data/test.watchr +0 -70
  62. data/test/benchmark/colorize.rb +0 -39
@@ -7,18 +7,28 @@ module Riot
7
7
  # context "Foo" do
8
8
  # set :transactional, true
9
9
  # end
10
- def set(key, value) options[key] = value; end
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) options[key]; end
22
+ def option(key)
23
+ options[key]
24
+ end
17
25
 
18
- # Returns the has of defined options.
26
+ # Returns the hash of defined options.
19
27
  #
20
28
  # @return [Hash]
21
- def options; @options ||= {}; end
29
+ def options
30
+ @options ||= {}
31
+ end
22
32
 
23
33
  end # ContextOptions
24
34
  end # Riot
@@ -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 = []; _inspect(phrases)
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
- def method_missing(meth, *phrases, &blk) push(meth.to_s.gsub('_', ' ')); _inspect(phrases); end
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
- def comma(str, *phrases) _concat([", ", str]); _inspect(phrases); end
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
- def push(str) _concat([" ", str]); end
86
+
19
87
  private
20
- def _concat(chunks) @chunks.concat(chunks); self; end
21
- def _inspect(phrases) phrases.each { |phrase| push(phrase.inspect) }; self; end
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
@@ -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; Context.middlewares << self; end
70
+ def self.register
71
+ Context.middlewares << self
72
+ end
16
73
 
17
- attr_reader :middleware # Theoretically, the next middleware in the stack
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 meat. Because you have access to the Context, you can add your own setups,
24
- # hookups, etc. +call+ will be called before any tests are run, but after the Context is configured.
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
 
@@ -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
- attr_accessor :passes, :failures, :errors, :current_context
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
- def success?; (@failures + @errors) == 0; end
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
- def describe_context(context); @current_context = context; end
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
- def pass(description, result); end
40
- def fail(description, message, line, file); end
41
- def error(description, result); end
42
- end # Reporter
43
-
44
- class IOReporter < Reporter
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
- puts " - " + yellow("#{description}: #{message} #{line_info(line, file)}".strip)
91
+ raise "Implement this in a sub-class"
107
92
  end
108
93
 
109
- def error(description, e) puts " ! " + red("#{description}: #{e.message}"); end
110
- end
111
-
112
- class VerboseStoryReporter < StoryReporter
113
- def error(description, e)
114
- super
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
- puts "\n#{@details.join("\n\n")}" unless @details.empty?
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'