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
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
- require 'rubygems'
2
- require 'rake'
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
3
 
4
- task :default => ["test:all"]
4
+ task :default => ["test:all"]
5
5
  task "test:all" => ["test:core", "test:extensions"]
6
6
 
7
7
  require 'rake/testtask'
@@ -32,40 +32,5 @@ end
32
32
  desc "Run all of them fancy benchmarks, Howard!"
33
33
  task("test:benchmarks") { run_benchmarks("ruby") }
34
34
 
35
- desc "Run all of them fancy benchmarks in ruby-1.9, Steve!"
36
- task("test:benchmarks:1.9") { run_benchmarks("ruby1.9") }
37
-
38
- #
39
- # YARDie
40
-
41
- begin
42
- require 'yard'
43
- require 'yard/rake/yardoc_task'
44
- YARD::Rake::YardocTask.new do |t|
45
- extra_files = %w(MIT-LICENSE)
46
- t.files = ['lib/**/*.rb']
47
- t.options = ["--files=#{extra_files.join(',')}"]
48
- end
49
- rescue LoadError
50
- # YARD isn't installed
51
- end
52
-
53
35
  #
54
36
  # Some monks like diamonds. I like gems.
55
-
56
- begin
57
- require 'jeweler'
58
- Jeweler::Tasks.new do |gem|
59
- gem.name = "riot"
60
- gem.summary = "An extremely fast, expressive, and context-driven unit-testing framework. Protest the slow test."
61
- gem.description = "An extremely fast, expressive, and context-driven unit-testing framework. A replacement for all other testing frameworks. Protest the slow test."
62
- gem.email = "gus@gusg.us"
63
- gem.homepage = "http://github.com/thumblemonks/riot"
64
- gem.authors = ["Justin 'Gus' Knowlden"]
65
- gem.add_dependency 'rr'
66
- gem.add_dependency 'term-ansicolor'
67
- end
68
- Jeweler::GemcutterTasks.new
69
- rescue LoadError
70
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
71
- end
@@ -6,13 +6,33 @@ require 'riot/runnable'
6
6
  require 'riot/assertion'
7
7
  require 'riot/assertion_macro'
8
8
 
9
+ # The namespace for all of Riot.
9
10
  module Riot
11
+ # A helper for creating/defining root context instances.
12
+ #
13
+ # @param [String] description the description of this context
14
+ # @param [Class] context_class the {Riot::Context} implementation to use
15
+ # @param [lambda] &definition the context definition
16
+ # @return [Context] the initialized {Riot::Context}
10
17
  def self.context(description, context_class = Context, &definition)
11
18
  (root_contexts << context_class.new(description, &definition)).last
12
19
  end
13
20
 
14
- def self.root_contexts; @root_contexts ||= []; end
21
+ # The set of {Riot::Context} instances that have no parent.
22
+ #
23
+ # @return [Array] instances of {Riot::Context}
24
+ def self.root_contexts
25
+ @root_contexts ||= []
26
+ end
15
27
 
28
+ # How to run Riot itself. This should be called +at_exit+ unless you suggested - by calling {Riot.alone!}
29
+ # that you want to call this method yourself. If no {Riot.reporter} is set, the
30
+ # {Riot::StoryReporter default} will be used.
31
+ #
32
+ # You can change reporters by setting the manually via {Riot.reporter=} or by using one of: {Riot.dots},
33
+ # {Riot.silently!}, or {Riot.verbose}.
34
+ #
35
+ # @return [Riot::Reporter] the reporter that was used
16
36
  def self.run
17
37
  the_reporter = reporter.new
18
38
  the_reporter.summarize do
@@ -21,16 +41,41 @@ module Riot
21
41
  the_reporter
22
42
  end
23
43
 
24
- # This means you don't want to see any output from Riot. A "quiet riot" as Envy5 put it.
25
- def self.silently!; @silent = true; end
26
- def self.silently?; defined?(@silent) && @silent == true end
44
+ # This means you don't want to see any output from Riot. A "quiet riot".
45
+ def self.silently!
46
+ @silent = true
47
+ end
48
+
49
+ # Reponds to whether Riot is reporting silently.
50
+ #
51
+ # @return [Boolean]
52
+ def self.silently?
53
+ defined?(@silent) && @silent == true
54
+ end
27
55
 
28
56
  # This means you don't want Riot to run tests for you. You will execute Riot.run manually.
29
- def self.alone!; @alone = true; end
30
- def self.alone?; defined?(@alone) && @alone == true end
57
+ def self.alone!
58
+ @alone = true
59
+ end
31
60
 
32
- def self.reporter=(reporter_class) @reporter_class = reporter_class; end
61
+ # Responds to whether Riot will run +at_exit+ (false) or manually (true).
62
+ #
63
+ # @return [Boolean]
64
+ def self.alone?
65
+ defined?(@alone) && @alone == true
66
+ end
33
67
 
68
+ # Allows the reporter class to be changed. Do this before tests are started.
69
+ #
70
+ # @param [Class] reporter_class the Class that represents a {Riot::Reporter}
71
+ def self.reporter=(reporter_class)
72
+ @reporter_class = reporter_class
73
+ end
74
+
75
+ # Returns the class for the reporter that is currently selected. If no reporter was explicitly selected,
76
+ # {Riot::StoryReporter} will be used.
77
+ #
78
+ # @return [Class] the Class that represents a {Riot::Reporter}
34
79
  def self.reporter
35
80
  if Riot.silently?
36
81
  Riot::SilentReporter
@@ -39,16 +84,34 @@ module Riot
39
84
  end
40
85
  end
41
86
 
42
- # TODO: make this a flag that DotMatrix and Story respect and cause them to print errors/failures
43
- def self.verbose; Riot.reporter = Riot::VerboseStoryReporter; end
44
- def self.dots; Riot.reporter = Riot::DotMatrixReporter; end
87
+ # @todo make this a flag that DotMatrix and Story respect and cause them to print errors/failures
88
+ # Tells Riot to use {Riot::VerboseStoryReporter} for reporting
89
+ def self.verbose
90
+ Riot.reporter = Riot::VerboseStoryReporter
91
+ end
92
+
93
+ # Tells Riot to use {Riot::DotMatrixReporter} for reporting
94
+ def self.dots
95
+ Riot.reporter = Riot::DotMatrixReporter
96
+ end
97
+
98
+ # Tells Riot to use {Riot::PrettyDotMatrixReporter} for reporting
99
+ def self.pretty_dots
100
+ Riot.reporter = Riot::PrettyDotMatrixReporter
101
+ end
45
102
 
46
103
  at_exit { exit(run.success?) unless Riot.alone? }
47
104
  end # Riot
48
105
 
106
+ # A little bit of monkey-patch so we can have +context+ available anywhere.
49
107
  class Object
108
+ # Defining +context+ in Object itself lets us define a root +context+ in any file. Any +context+ defined
109
+ # within a +context+ is already handled by {Riot::Context#context}.
110
+ #
111
+ # @param (see Riot.context)
112
+ # @return (see Riot.context)
50
113
  def context(description, context_class = Riot::Context, &definition)
51
114
  Riot.context(description, context_class, &definition)
52
115
  end
53
116
  alias_method :describe, :context
54
- end
117
+ end # Object
@@ -1,13 +1,36 @@
1
1
  module Riot
2
+ # An Assertion is the engine behind evaluating a single test that can be reported on. When +asserts+
3
+ # or +denies+ is used, the description and assertion block are used to generate a single Assertion
4
+ # instance. When running an Assertion, a [Riot::Situation] instance is required for data scoping. The
5
+ # result of calling run will be a status tuple that can be used for reporting.
6
+ #
7
+ # In general, you won't be spending much time in here.
2
8
  class Assertion < RunnableBlock
3
9
  class << self
10
+ # Returns the list of assertion macros that have been successfully registered
11
+ #
12
+ # @return [Hash<Riot::AssertionMacro>]
4
13
  def macros; @@macros ||= {}; end
5
14
 
6
- def register_macro(name, assertion_macro, expect_exception=false)
15
+ # Registers a [Riot::AssertionMacro] class to a given +name+. Name is distinct, which means any future
16
+ # registrations for the same name will replace previous ones. +name+ will always be converted to a
17
+ # string first.
18
+ #
19
+ # @param [String, Symbol] name The handle the macro will be associated with
20
+ # @param [Class] assertion_macro A [Riot::AssertionMacro] class
21
+ def register_macro(name, assertion_macro)
7
22
  macros[name.to_s] = assertion_macro
8
23
  end
9
24
  end
10
25
 
26
+ # Setups a new Assertion. By default, the assertion will be a "positive" one, which means +evaluate+ will
27
+ # be call on the associated assertion macro. If +negative+ is true, +devaluate+ will be called instead.
28
+ # Not providing a definition block is just kind of silly since it's used to generate the +actual+ value
29
+ # for evaluation by a macro.
30
+ #
31
+ # @param [String] definition A small description of what this assertion is testing
32
+ # @param [Boolean] negative Determines whether this is a positive or negative assertion
33
+ # @param [lambda] definition The block that will return the +actual+ value when eval'ed
11
34
  def initialize(description, negative=false, &definition)
12
35
  super(description, &definition)
13
36
  @negative = negative
@@ -15,6 +38,14 @@ module Riot
15
38
  @macro = AssertionMacro.default
16
39
  end
17
40
 
41
+ # Given a {Riot::Situation}, execute the assertion definition provided to this Assertion, hand off to an
42
+ # assertion macro for evaluation, and then return a status tuple. If the macro to be used expects any
43
+ # exception, catch the exception and send to the macro; else just return it back.
44
+ #
45
+ # Currently supporting 3 evaluation states: :pass, :fail, and :error
46
+ #
47
+ # @param [Riot::Situation] situation An instance of a {Riot::Situation}
48
+ # @return [Array<Symbol, String>] array containing evaluation state and a descriptive explanation
18
49
  def run(situation)
19
50
  @expectings << situation.evaluate(&@expectation_block) if @expectation_block
20
51
  actual = situation.evaluate(&definition)
@@ -6,15 +6,15 @@ module Riot
6
6
  # == Using macros
7
7
  #
8
8
  # Macros are applied to the return value of assertions. For example, the
9
- # `empty` macro asserts that the value is, well, empty, e.g.
9
+ # +empty+ macro asserts that the value is empty or denies that it is empty e.g.
10
10
  #
11
11
  # asserts(:comments).empty?
12
- #
12
+ # denies(:comments).empty?
13
13
  #
14
14
  # == Writing your own macros
15
15
  #
16
16
  # Macros are added by subclassing {AssertionMacro}. For example, here's
17
- # the implementation of `empty`:
17
+ # the implementation of +empty+:
18
18
  #
19
19
  # class EmptyMacro < AssertionMacro
20
20
  # register :empty
@@ -31,9 +31,14 @@ module Riot
31
31
  class AssertionMacro
32
32
  class << self
33
33
  # Whether the macro expects an exception to be thrown.
34
+ #
35
+ # @return [Boolean]
34
36
  attr_reader :expects_exception
35
37
 
38
+ # @private
36
39
  # The default macro.
40
+ #
41
+ # @return [Riot::AssertionMacro]
37
42
  def default
38
43
  @default_macro ||= new
39
44
  end
@@ -45,34 +50,76 @@ module Riot
45
50
 
46
51
  # Register the macro under the given name.
47
52
  #
48
- # @param [Symbol] name the name of the macro
53
+ # @param [String, Symbol] name the name of the macro
49
54
  def register(name)
50
55
  Assertion.register_macro name, self
51
56
  end
52
57
  end
53
58
 
54
- attr_accessor :line, :file
59
+ # During failure reporting, what line number did the failure occur at
60
+ # @return [Number]
61
+ attr_accessor :line
55
62
 
63
+ # During failure reporting, what file did the failure occur in
64
+ # @return [String]
65
+ attr_accessor :file
66
+
67
+ # Returns a status tuple indicating the assertion passed.
68
+ #
69
+ # @param [String] message the message to report with
70
+ # @return [Array[Symbol, String]]
56
71
  def pass(message=nil) [:pass, message.to_s]; end
72
+
73
+ # Returns a status tuple indicating the assertion failed and where it failed it if that can be
74
+ # determined.
75
+ #
76
+ # @param [String] message the message to report with
77
+ # @return [Array[Symbol, String, Number, String]]
57
78
  def fail(message) [:fail, message.to_s, line, file]; end
58
- def error(e) [:error, e]; end
59
79
 
80
+ # Returns a status tuple indicating the assertion had an unexpected error.
81
+ #
82
+ # @param [Exception] ex the Exception that was captured
83
+ # @return [Array[Symbol, Exception]]
84
+ def error(ex) [:error, ex]; end
85
+
86
+ # Returns +true+ if this macro expects to handle Exceptions during evaluation.
87
+ #
88
+ # @return [boolean]
60
89
  def expects_exception?; self.class.expects_exception; end
61
90
 
62
- # Supports positive assertion testing
91
+ # Supports positive assertion testing. This is where magic happens.
92
+ #
93
+ # @param [Object] actual the value returned from evaling the {Riot::Assertion Assertion} block
94
+ # @return [Array] response from either {#pass}, {#fail} or {#error}
63
95
  def evaluate(actual)
64
96
  actual ? pass : fail("Expected non-false but got #{actual.inspect} instead")
65
97
  end
66
98
 
67
- # Supports negative/converse assertion testing
99
+ # Supports negative/converse assertion testing. This is also where magic happens.
100
+ #
101
+ # @param [Object] actual the value returned from evaling the {Riot::Assertion Assertion} block
102
+ # @return [Array] response from either {#pass}, {#fail} or {#error}
68
103
  def devaluate(actual)
69
104
  !actual ? pass : fail("Expected non-true but got #{actual.inspect} instead")
70
105
  end
71
106
 
72
- # Messaging
73
-
107
+ # Creates a new message for use in any macro response that is initially empty.
108
+ #
109
+ # @param [Array<Object>] *phrases array of object whose values will be inspected and added to message
110
+ # @return [Riot::Message]
74
111
  def new_message(*phrases) Message.new(*phrases); end
112
+
113
+ # Creates a new message for use in any macro response that will start as "should have ".
114
+ #
115
+ # @param [Array<Object>] *phrases array of object whose values will be inspected and added to message
116
+ # @return [Riot::Message]
75
117
  def should_have_message(*phrases) new_message.should_have(*phrases); end
118
+
119
+ # Creates a new message for use in any macro response that will start as "expected ".
120
+ #
121
+ # @param [Array<Object>] *phrases array of object whose values will be inspected and added to message
122
+ # @return [Riot::Message]
76
123
  def expected_message(*phrases) new_message.expected(*phrases); end
77
124
  end
78
125
  end
@@ -11,12 +11,14 @@ module Riot
11
11
  class AnyMacro < AssertionMacro
12
12
  register :any
13
13
 
14
+ # (see Riot::AssertionMacro#evaluate)
14
15
  def evaluate(actual)
15
- any?(actual) ? pass("is not empty") : fail(expected_message(actual).to_have_items)
16
+ any?(actual) ? pass("has items") : fail(expected_message(actual).to_have_items)
16
17
  end
17
18
 
19
+ # (see Riot::AssertionMacro#devaluate)
18
20
  def devaluate(actual)
19
- any?(actual) ? fail(expected_message(actual).not_to_have_elements) : pass("has elements")
21
+ any?(actual) ? fail(expected_message(actual).not_to_have_items) : pass("has items")
20
22
  end
21
23
  private
22
24
  def any?(object)
@@ -20,25 +20,39 @@ module Riot
20
20
  class AssignsMacro < AssertionMacro
21
21
  register :assigns
22
22
 
23
+ # (see Riot::AssertionMacro#evaluate)
24
+ # @param [Symbol, String] variable name of instance variable to look for
25
+ # @param [Object, nil] expected_value an optional value to validate for the variable
23
26
  def evaluate(actual, *expectings)
24
27
  prepare(actual, *expectings) do |variable, expected_value, actual_value|
25
28
  if actual_value.nil?
26
29
  fail expected_message(variable).to_be_assigned_a_value
27
30
  elsif !expected_value.nil? && expected_value != actual_value
28
- fail expected_message(variable).to_be_equal_to(expected_value).not(actual_value)
31
+ fail expected_message(variable).to_be_assigned_with(expected_value).not(actual_value)
29
32
  else
30
- pass
33
+ if expected_value && actual_value
34
+ pass new_message.assigns(variable).with(expected_value)
35
+ else
36
+ pass new_message.assigns(variable)
37
+ end
31
38
  end
32
39
  end
33
40
  end
34
41
 
42
+ # (see Riot::AssertionMacro#devaluate)
43
+ # @param [Symbol, String] variable name of instance variable to look for
44
+ # @param [Object, nil] expected_value an optional value to validate for the variable
35
45
  def devaluate(actual, *expectings)
36
46
  prepare(actual, *expectings) do |variable, expected_value, actual_value|
37
47
  if actual_value.nil? || (expected_value && expected_value != actual_value)
38
- pass
48
+ if expected_value && actual_value
49
+ pass new_message.assigns(variable).with(expected_value)
50
+ else
51
+ pass new_message.assigns(variable)
52
+ end
39
53
  else
40
54
  message = expected_message(variable).to_not_be
41
- fail(expected_value.nil? ? message.assigned_a_value : message.equal_to(expected_value))
55
+ fail(expected_value.nil? ? message.assigned_a_value : message.assigned_with(expected_value))
42
56
  end
43
57
  end
44
58
  end
@@ -13,10 +13,12 @@ module Riot
13
13
  class EmptyMacro < AssertionMacro
14
14
  register :empty
15
15
 
16
+ # (see Riot::AssertionMacro#evaluate)
16
17
  def evaluate(actual)
17
18
  actual.empty? ? pass(new_message.is_empty) : fail(expected_message(actual).to_be_empty)
18
19
  end
19
20
 
21
+ # (see Riot::AssertionMacro#devaluate)
20
22
  def devaluate(actual)
21
23
  actual.empty? ? fail(expected_message(actual).to_not_be_empty) : pass(new_message.is_empty)
22
24
  end
@@ -14,6 +14,8 @@ module Riot
14
14
  class EqualsMacro < AssertionMacro
15
15
  register :equals
16
16
 
17
+ # (see Riot::AssertionMacro#evaluate)
18
+ # @param [Object] expected the object value to compare actual to
17
19
  def evaluate(actual, expected)
18
20
  if expected == actual
19
21
  pass new_message.is_equal_to(expected)
@@ -22,6 +24,8 @@ module Riot
22
24
  end
23
25
  end
24
26
 
27
+ # (see Riot::AssertionMacro#devaluate)
28
+ # @param [Object] expected the object value to compare actual to
25
29
  def devaluate(actual, expected)
26
30
  if expected != actual
27
31
  pass new_message.is_equal_to(expected).when_it_is(actual)
@@ -16,6 +16,8 @@ module Riot
16
16
  class EquivalentToMacro < AssertionMacro
17
17
  register :equivalent_to
18
18
 
19
+ # (see Riot::AssertionMacro#evaluate)
20
+ # @param [Object] expected the object value to compare actual to
19
21
  def evaluate(actual, expected)
20
22
  if expected === actual
21
23
  pass new_message.is_equivalent_to(expected)
@@ -24,11 +26,13 @@ module Riot
24
26
  end
25
27
  end
26
28
 
29
+ # (see Riot::AssertionMacro#devaluate)
30
+ # @param [Object] expected the object value to compare actual to
27
31
  def devaluate(actual, expected)
28
32
  if expected === actual
29
33
  fail expected_message(actual).not_to_be_equivalent_to(expected)
30
34
  else
31
- pass new_message.is_not_equivalent_to(expected)
35
+ pass new_message.is_equivalent_to(expected)
32
36
  end
33
37
  end
34
38