riot 0.10.11 → 0.10.12.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +3 -1
  2. data/CHANGELOG +34 -0
  3. data/README.markdown +81 -456
  4. data/Rakefile +31 -28
  5. data/VERSION +1 -1
  6. data/lib/riot.rb +2 -1
  7. data/lib/riot/assertion.rb +1 -1
  8. data/lib/riot/assertion_macro.rb +51 -5
  9. data/lib/riot/assertion_macros/any.rb +1 -1
  10. data/lib/riot/assertion_macros/assigns.rb +2 -2
  11. data/lib/riot/assertion_macros/empty.rb +1 -1
  12. data/lib/riot/assertion_macros/equals.rb +4 -4
  13. data/lib/riot/assertion_macros/equivalent_to.rb +22 -0
  14. data/lib/riot/assertion_macros/includes.rb +2 -2
  15. data/lib/riot/assertion_macros/kind_of.rb +2 -2
  16. data/lib/riot/assertion_macros/matches.rb +2 -2
  17. data/lib/riot/assertion_macros/nil.rb +1 -1
  18. data/lib/riot/assertion_macros/raises.rb +7 -10
  19. data/lib/riot/assertion_macros/respond_to.rb +3 -2
  20. data/lib/riot/assertion_macros/same_elements.rb +1 -1
  21. data/lib/riot/assertion_macros/size.rb +2 -2
  22. data/lib/riot/context.rb +104 -19
  23. data/lib/riot/message.rb +23 -0
  24. data/lib/riot/rr.rb +35 -0
  25. data/lib/riot/runnable.rb +12 -0
  26. data/lib/riot/situation.rb +4 -0
  27. data/riot.gemspec +20 -4
  28. data/test/assertion_macro_test.rb +30 -0
  29. data/test/assertion_macros/assigns_test.rb +3 -3
  30. data/test/assertion_macros/equals_test.rb +2 -9
  31. data/test/assertion_macros/equivalent_to_test.rb +36 -0
  32. data/test/assertion_macros/respond_to_test.rb +1 -0
  33. data/test/assertion_macros/size_test.rb +6 -6
  34. data/test/assertion_test.rb +1 -1
  35. data/test/benchmark/message_concatenation.rb +82 -0
  36. data/test/context_test.rb +17 -2
  37. data/test/extensions/rrriot_test.rb +69 -0
  38. data/test/message_test.rb +35 -0
  39. data/test/teststrap.rb +1 -1
  40. metadata +27 -4
data/Rakefile CHANGED
@@ -1,47 +1,49 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
3
 
4
- task :default => [:test]
4
+ task :default => ["test:core", "test:assertion_macros"]
5
+ task "test:all" => ["test:core", "test:assertion_macros", "test:extensions"]
5
6
 
6
7
  require 'rake/testtask'
7
- Rake::TestTask.new(:test) do |test|
8
+ Rake::TestTask.new("test:core") do |test|
8
9
  test.libs << 'test'
9
- test.pattern = 'test/**/*_test.rb'
10
+ test.pattern = 'test/*_test.rb'
10
11
  test.warning = true
11
12
  test.verbose = false
12
13
  end
13
14
 
14
- desc "Run all of them fancy benchmarks, Howard!"
15
- task :benchmarks do
16
- Dir["test/benchmark/*.rb"].each do |file|
17
- puts ">> Running #{file}"
18
- puts %x[ruby #{file}]
19
- end
20
- end
21
-
22
- desc "Run Flog against library (except tests)"
23
- task :flog do
24
- puts %x[find ./lib -name *.rb | xargs flog]
15
+ Rake::TestTask.new("test:assertion_macros") do |test|
16
+ test.libs << 'test'
17
+ test.pattern = 'test/assertion_macros/*_test.rb'
18
+ test.warning = true
19
+ test.verbose = false
25
20
  end
26
21
 
27
- desc "Run Flay against library (except tests)"
28
- task :flay do
29
- puts %x[find ./lib -name *.rb | xargs flay]
22
+ Rake::TestTask.new("test:extensions") do |test|
23
+ test.libs << 'test'
24
+ test.pattern = 'test/extensions/*_test.rb'
25
+ test.warning = true
26
+ test.verbose = false
30
27
  end
31
28
 
32
- desc "Run Roodi against library (except tests)"
33
- task :roodi do
34
- puts %x[find ./lib -name *.rb | xargs roodi]
29
+ begin
30
+ require 'yard'
31
+ require 'yard/rake/yardoc_task'
32
+ YARD::Rake::YardocTask.new do |t|
33
+ extra_files = %w(MIT-LICENSE)
34
+ t.files = ['lib/**/*.rb']
35
+ t.options = ["--files=#{extra_files.join(',')}"]
36
+ end
37
+ rescue LoadError
38
+ # YARD isn't installed
35
39
  end
36
40
 
37
- desc "Stats on lines of code and test"
38
- task :stats do
39
- loc = %x[find ./lib -name *.rb | xargs cat | wc -l].strip.to_i
40
- lotc = %x[find ./test -name *.rb | xargs cat | wc -l].strip.to_i
41
- total, ratio = (loc + lotc), (lotc / loc.to_f)
42
-
43
- fmt = " Code: %d\n Test: %d\n -----\n Total: %d Ratio (test/code): %f"
44
- puts fmt % [loc, lotc, loc + lotc, ratio]
41
+ desc "Run all of them fancy benchmarks, Howard!"
42
+ task :benchmarks do
43
+ Dir["test/benchmark/*.rb"].each do |file|
44
+ puts ">> Running #{file}"
45
+ puts %x[ruby #{file}]
46
+ end
45
47
  end
46
48
 
47
49
  #
@@ -56,6 +58,7 @@ begin
56
58
  gem.email = "gus@gusg.us"
57
59
  gem.homepage = "http://github.com/thumblemonks/riot"
58
60
  gem.authors = ["Justin 'Gus' Knowlden"]
61
+ gem.add_dependency 'rr'
59
62
  gem.add_dependency 'term-ansicolor'
60
63
  end
61
64
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.11
1
+ 0.10.12.pre
@@ -7,7 +7,7 @@ require 'riot/assertion_macro'
7
7
 
8
8
  module Riot
9
9
  def self.context(description, context_class = Context, &definition)
10
- root_contexts << context_class.new(description, &definition)
10
+ (root_contexts << context_class.new(description, &definition)).last
11
11
  end
12
12
 
13
13
  def self.root_contexts; @root_contexts ||= []; end
@@ -20,6 +20,7 @@ module Riot
20
20
  the_reporter
21
21
  end
22
22
 
23
+ # This means you don't want to see any output from Riot. A "quiet riot" as Envy5 put it.
23
24
  def self.silently!; @silent = true; end
24
25
  def self.silently?; defined?(@silent) && @silent == true end
25
26
 
@@ -1,7 +1,7 @@
1
1
  module Riot
2
2
  class Assertion < RunnableBlock
3
3
  class << self
4
- def macros; @macros ||= {}; end
4
+ def macros; @@macros ||= {}; end
5
5
 
6
6
  def register_macro(name, assertion_macro, expect_exception=false)
7
7
  macros[name.to_s] = assertion_macro.new
@@ -1,15 +1,54 @@
1
+ require 'riot/message'
2
+
1
3
  module Riot
4
+ # The base class for all assertion macros.
5
+ #
6
+ # == Using macros
7
+ #
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.
10
+ #
11
+ # asserts(:comments).empty?
12
+ #
13
+ #
14
+ # == Writing your own macros
15
+ #
16
+ # Macros are added by subclassing {AssertionMacro}. For example, here's
17
+ # the implementation of `empty`:
18
+ #
19
+ # class EmptyMacro < AssertionMacro
20
+ # register :empty
21
+ #
22
+ # def evaluate(actual)
23
+ # actual.length == 0 ? pass : fail(expected_message(actual).to_be_empty)
24
+ # end
25
+ # end
26
+ #
2
27
  class AssertionMacro
3
28
  class << self
29
+ # Whether the macro expects an exception to be thrown.
4
30
  attr_reader :expects_exception
5
31
 
6
- def default; @default_macro ||= new; end
7
- def expects_exception!; @expects_exception = true; end
8
- def register(name); Assertion.register_macro name, self; end
32
+ # The default macro.
33
+ def default
34
+ @default_macro ||= new
35
+ end
36
+
37
+ # Specify that the macro expects an exception to be thrown by the assertion.
38
+ def expects_exception!
39
+ @expects_exception = true
40
+ end
41
+
42
+ # Register the macro under the given name.
43
+ #
44
+ # @param [Symbol] name the name of the macro
45
+ def register(name)
46
+ Assertion.register_macro name, self
47
+ end
9
48
  end
10
49
 
11
- def pass(message=nil) [:pass, message]; end
12
- def fail(message) [:fail, message]; end
50
+ def pass(message=nil) [:pass, message.to_s]; end
51
+ def fail(message) [:fail, message.to_s]; end
13
52
  def error(e) [:error, e]; end
14
53
 
15
54
  def expects_exception?; self.class.expects_exception; end
@@ -17,6 +56,12 @@ module Riot
17
56
  def evaluate(actual)
18
57
  actual ? pass : fail("Expected non-false but got #{actual.inspect} instead")
19
58
  end
59
+
60
+ # Messaging
61
+
62
+ def new_message(*phrases) Message.new(*phrases); end
63
+ def should_have_message(*phrases) new_message.should_have(*phrases); end
64
+ def expected_message(*phrases) new_message.expected(*phrases); end
20
65
  end
21
66
  end
22
67
 
@@ -24,6 +69,7 @@ require 'riot/assertion_macros/any'
24
69
  require 'riot/assertion_macros/assigns'
25
70
  require 'riot/assertion_macros/empty'
26
71
  require 'riot/assertion_macros/equals'
72
+ require 'riot/assertion_macros/equivalent_to'
27
73
  require 'riot/assertion_macros/exists'
28
74
  require 'riot/assertion_macros/includes'
29
75
  require 'riot/assertion_macros/kind_of'
@@ -6,7 +6,7 @@ module Riot
6
6
  register :any
7
7
 
8
8
  def evaluate(actual)
9
- actual.any? ? pass("is not empty") : fail("expected #{actual.inspect} to have items")
9
+ actual.any? ? pass("is not empty") : fail(expected_message(actual).to_have_items)
10
10
  end
11
11
  end
12
12
  end
@@ -16,9 +16,9 @@ module Riot
16
16
  variable_name = "@#{variable}"
17
17
  actual_value = actual.instance_variable_defined?(variable_name) ? actual.instance_variable_get(variable_name) : nil
18
18
  if actual_value.nil?
19
- fail("expected @#{variable} to be assigned a value")
19
+ fail expected_message(variable).to_be_assigned_a_value
20
20
  elsif !expected_value.nil? && expected_value != actual_value
21
- fail(%Q[expected @#{variable} to be equal to #{expected_value.inspect}, not #{actual_value.inspect}])
21
+ fail expected_message(variable).to_be_equal_to(expected_value).not(actual_value)
22
22
  else
23
23
  pass
24
24
  end
@@ -7,7 +7,7 @@ module Riot
7
7
  register :empty
8
8
 
9
9
  def evaluate(actual)
10
- actual.length == 0 ? pass : fail("expected #{actual.inspect} to be empty")
10
+ actual.length == 0 ? pass : fail(expected_message(actual).to_be_empty)
11
11
  end
12
12
  end
13
13
  end
@@ -1,5 +1,5 @@
1
1
  module Riot
2
- # Asserts that the result of the test equals the expected value. Using the +===+ operator to assert
2
+ # Asserts that the result of the test equals the expected value. Using the +==+ operator to assert
3
3
  # equality.
4
4
  # asserts("test") { "foo" }.equals("foo")
5
5
  # should("test") { "foo" }.equals("foo")
@@ -8,10 +8,10 @@ module Riot
8
8
  register :equals
9
9
 
10
10
  def evaluate(actual, expected)
11
- if expected === actual
12
- pass("is equal to #{expected.inspect}")
11
+ if expected == actual
12
+ pass new_message.is_equal_to(expected)
13
13
  else
14
- fail("expected #{expected.inspect}, not #{actual.inspect}")
14
+ fail expected_message(expected).not(actual)
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,22 @@
1
+ module Riot
2
+ # Asserts that the result of the test is equivalent to the expected value. Using the +===+ operator.
3
+ #
4
+ # asserts("test") { "foo" }.equivalent_to(String)
5
+ # should("test") { "foo" }.equivalent_to("foo")
6
+ # asserts("test") { "foo" }.equivalent_to { "foo" }
7
+ #
8
+ # Underneath the hood, this assertion macro says:
9
+ #
10
+ # expected === actual
11
+ class EquivalentToMacro < AssertionMacro
12
+ register :equivalent_to
13
+
14
+ def evaluate(actual, expected)
15
+ if expected === actual
16
+ pass new_message.is_equivalent_to(expected)
17
+ else
18
+ fail expected_message(actual).to_be_equivalent_to(expected)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -8,9 +8,9 @@ module Riot
8
8
 
9
9
  def evaluate(actual, expected)
10
10
  if actual.include?(expected)
11
- pass("includes #{expected.inspect}")
11
+ pass new_message.includes(expected)
12
12
  else
13
- fail("expected #{actual.inspect} to include #{expected.inspect}")
13
+ fail expected_message(actual).to_include(expected)
14
14
  end
15
15
  end
16
16
  end
@@ -7,9 +7,9 @@ module Riot
7
7
 
8
8
  def evaluate(actual, expected)
9
9
  if actual.kind_of?(expected)
10
- pass("is a kind of #{expected.inspect}")
10
+ pass new_message.is_a_kind_of(expected)
11
11
  else
12
- fail("expected kind of #{expected}, not #{actual.class.inspect}")
12
+ fail expected_message.kind_of(expected).not(actual.class)
13
13
  end
14
14
  end
15
15
  end
@@ -8,9 +8,9 @@ module Riot
8
8
  def evaluate(actual, expected)
9
9
  expected = %r[#{Regexp.escape(expected)}] if expected.kind_of?(String)
10
10
  if actual =~ expected
11
- pass("matches #{expected.inspect}")
11
+ pass(new_message.matches(expected))
12
12
  else
13
- fail("expected #{expected.inspect} to match #{actual.inspect}")
13
+ fail(expected_message(expected).to_match(actual))
14
14
  end
15
15
  end
16
16
  end
@@ -6,7 +6,7 @@ module Riot
6
6
  register :nil
7
7
 
8
8
  def evaluate(actual)
9
- actual.nil? ? pass("is nil") : fail("expected nil, not #{actual.inspect}")
9
+ actual.nil? ? pass("is nil") : fail(expected_message.nil.not(actual))
10
10
  end
11
11
  end
12
12
  end
@@ -14,18 +14,15 @@ module Riot
14
14
  def evaluate(actual_exception, expected_class, expected_message=nil)
15
15
  actual_message = actual_exception && actual_exception.message
16
16
  if actual_exception.nil?
17
- fail("should have raised #{expected_class}, but raised nothing")
17
+ fail should_have_message.raised(expected_class).but.raised_nothing
18
18
  elsif expected_class != actual_exception.class
19
- fail("should have raised #{expected_class}, not #{actual_exception.class}")
19
+ fail should_have_message.raised(expected_class).not(actual_exception.class)
20
20
  elsif expected_message && !(actual_message.to_s =~ %r[#{expected_message}])
21
- fail("expected #{expected_message.inspect} for message, not #{actual_message.inspect}")
21
+ fail expected_message(expected_message).for_message.not(actual_message)
22
22
  else
23
- if expected_message
24
- pass("raises #{expected_class.inspect} with message #{expected_message.inspect}")
25
- else
26
- pass("raises #{expected_class.inspect}")
27
- end
23
+ message = new_message.raises(expected_class)
24
+ pass(expected_message ? message.with_message(expected_message) : message)
28
25
  end
29
- end
30
- end
26
+ end # evaluate
27
+ end # RaisesMacro
31
28
  end
@@ -4,12 +4,13 @@ module Riot
4
4
  # should("test") { "foo" }.respond_to(:to_s)
5
5
  class RespondToMacro < AssertionMacro
6
6
  register :respond_to
7
+ register :responds_to
7
8
 
8
9
  def evaluate(actual, expected)
9
10
  if actual.respond_to?(expected)
10
- pass("responds to #{expected.inspect}")
11
+ pass(new_message.responds_to(expected))
11
12
  else
12
- fail("expected method #{expected.inspect} is not defined")
13
+ fail(expected_message.method(expected).is_not_defined)
13
14
  end
14
15
  end
15
16
  end
@@ -8,7 +8,7 @@ module Riot
8
8
  def evaluate(actual, expected)
9
9
  require 'set'
10
10
  same = (Set.new(expected) == Set.new(actual))
11
- same ? pass : fail("expected elements #{expected.inspect} to match #{actual.inspect}")
11
+ same ? pass : fail(expected_message.elements(expected).to_match(actual))
12
12
  end
13
13
  end
14
14
  end
@@ -8,8 +8,8 @@ module Riot
8
8
  register :size
9
9
 
10
10
  def evaluate(actual, expected)
11
- failure_message = "size of #{actual.inspect} expected to be #{expected} but is #{actual.size}"
12
- expected === actual.size ? pass("is of size #{expected}") : fail(failure_message)
11
+ failure_message = expected_message.size_of(actual).to_be(expected).not(actual.size)
12
+ expected === actual.size ? pass(new_message.is_of_size(expected)) : fail(failure_message)
13
13
  end
14
14
  end
15
15
  end
@@ -1,19 +1,68 @@
1
1
  module Riot
2
2
  RootContext = Struct.new(:setups, :teardowns)
3
3
 
4
+ module ContextHelpers
5
+ def assertion_class; Assertion; end
6
+ def situation_class; Situation; end
7
+ end
8
+
9
+ # You make your assertions within a Context. The context stores setup and
10
+ # teardown blocks, and allows for nesting and refactoring into helpers.
4
11
  class Context
12
+ include ContextHelpers
13
+ # The description of the context.
14
+ #
15
+ # @return [String]
5
16
  attr_reader :description
6
- def initialize(description, parent=RootContext.new([],[]), &definition)
7
- @parent = parent
17
+
18
+ # The parent context.
19
+ #
20
+ # @return [Riot::Context]
21
+ attr_reader :parent
22
+
23
+ def initialize(description, parent=nil, &definition)
24
+ @parent = parent || RootContext.new([],[])
8
25
  @description = description
9
- @contexts, @setups, @assertions, @teardowns = [], [], [], []
26
+ @contexts, @helpers, @setups, @assertions, @teardowns = [], [], [], [], []
10
27
  self.instance_eval(&definition)
11
28
  end
12
29
 
13
- def setups; @parent.setups + @setups; end
14
- def teardowns; @parent.teardowns + @teardowns; end
30
+ # Create a new test context.
31
+ #
32
+ # @param [String] description
33
+ def context(description, &definition)
34
+ new_context(description, self.class, &definition)
35
+ end
36
+
37
+ # Returns an ordered list of the setup blocks for the context.
38
+ #
39
+ # @return [Array[Riot::RunnableBlock]]
40
+ def setups
41
+ @parent.setups + @setups
42
+ end
43
+
44
+ # Returns an ordered list of the teardown blocks for the context.
45
+ #
46
+ # @return [Array[Riot::RunnableBlock]]
47
+ def teardowns
48
+ @parent.teardowns + @teardowns
49
+ end
15
50
 
16
- def setup(&definition) (@setups << Setup.new(&definition)).last; end
51
+ # Add a setup block.
52
+ #
53
+ # A setup block defines the topic of the context. There can be multiple setup
54
+ # blocks; each can access the previous topic through the +topic+ attribute.
55
+ #
56
+ # context "A string" do
57
+ # setup { "foo" }
58
+ # setup { topic * 2 }
59
+ # asserts(:length).equals(6)
60
+ # end
61
+ def setup(&definition)
62
+ (@setups << Setup.new(&definition)).last
63
+ end
64
+
65
+ def helper(name, &block); (@helpers << Helper.new(name, &block)).last; end
17
66
 
18
67
  # A setup shortcut that returns the original topic so you don't have to. Good for nested setups. Instead
19
68
  # of doing this in your context:
@@ -30,32 +79,68 @@ module Riot
30
79
  setup { self.instance_eval(&definition); topic }
31
80
  end
32
81
 
33
- def teardown(&definition) (@teardowns << Setup.new(&definition)).last; end
82
+ # Add a teardown block.
83
+ def teardown(&definition)
84
+ (@teardowns << Setup.new(&definition)).last
85
+ end
34
86
 
35
- def asserts(what, &definition) new_assertion("asserts", what, &definition); end
36
- def should(what, &definition) new_assertion("should", what, &definition); end
37
- def asserts_topic(what="topic"); asserts(what) { topic }; end
87
+ # Makes an assertion.
88
+ #
89
+ # In the most basic form, an assertion requires a descriptive name and a block.
90
+ #
91
+ # asserts("#size is equals to 2") { topic.size == 2 }
92
+ #
93
+ # However, several shortcuts are available. Assertion macros can be added to the
94
+ # end, automating a number of common assertion patterns, e.g.
95
+ #
96
+ # asserts("#size") { topic.size }.equals(2)
97
+ #
98
+ # Furthermore, the pattern of testing an attribute on the topic is codified as
99
+ #
100
+ # asserts(:size).equals(2)
101
+ #
102
+ # Passing a Symbol to +asserts+ enables this behaviour. For more information on
103
+ # assertion macros, see {Riot::AssertionMacro}.
104
+ #
105
+ # @param [String, Symbol] what the property being tested
106
+ def asserts(what, &definition)
107
+ new_assertion("asserts", what, &definition)
108
+ end
38
109
 
39
- def context(description, &definition)
40
- @contexts << self.class.new("#{@description} #{description}", self, &definition)
110
+ def should(what, &definition)
111
+ new_assertion("should", what, &definition)
41
112
  end
42
-
113
+
114
+ # Makes an assertion on the topic itself, e.g.
115
+ #
116
+ # asserts_topic.matches(/^ab+/)
117
+ def asserts_topic(what="that it")
118
+ asserts(what) { topic }
119
+ end
120
+
43
121
  def run(reporter)
44
122
  reporter.describe_context(self) unless @assertions.empty?
45
- local_run(reporter, Situation.new)
123
+ local_run(reporter, situation_class.new)
46
124
  run_sub_contexts(reporter)
47
125
  reporter
48
126
  end
49
- private
50
127
 
51
128
  def local_run(reporter, situation)
52
- (setups + @assertions + teardowns).each do |runnable|
53
- reporter.report(runnable.to_s, runnable.run(situation))
54
- end
129
+ runnables.each { |runnable| reporter.report(runnable.to_s, runnable.run(situation)) }
130
+ end
131
+
132
+ private
133
+
134
+ def runnables
135
+ setups + @helpers + @assertions + teardowns
55
136
  end
56
137
 
57
138
  def run_sub_contexts(reporter) @contexts.each { |ctx| ctx.run(reporter) }; end
58
139
 
140
+ def new_context(description, klass, &definition)
141
+ (@contexts << klass.new("#{@description} #{description}", self, &definition)).last
142
+ end
143
+
59
144
  def new_assertion(scope, what, &definition)
60
145
  if what.kind_of?(Symbol)
61
146
  definition ||= lambda { topic.send(what) }
@@ -64,7 +149,7 @@ module Riot
64
149
  description = "#{scope} #{what}"
65
150
  end
66
151
 
67
- (@assertions << Assertion.new(description, &definition)).last
152
+ (@assertions << assertion_class.new(description, &definition)).last
68
153
  end
69
154
  end # Context
70
155
  end # Riot