attest 0.1.0 → 0.2.0

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 (45) hide show
  1. data/Gemfile +1 -1
  2. data/Gemfile.lock +6 -1
  3. data/README.rdoc +121 -36
  4. data/Rakefile +12 -1
  5. data/VERSION +1 -1
  6. data/attest.gemspec +61 -36
  7. data/bin/attest +18 -24
  8. data/doodle.txt +73 -29
  9. data/examples/{magic_calculator.rb → basic_functionality_example.rb} +42 -8
  10. data/examples/mocha_example.rb +43 -0
  11. data/examples/module_example.rb +49 -0
  12. data/examples/more/{placeholder.rb → multiple_context_example.rb} +0 -0
  13. data/examples/more/nesting/expectations_as_tests_example.rb +20 -0
  14. data/lib/attest.rb +4 -20
  15. data/lib/attest/config.rb +3 -6
  16. data/lib/attest/core_ext/kernel.rb +19 -1
  17. data/lib/attest/core_ext/object.rb +1 -2
  18. data/lib/attest/core_ext/proc.rb +35 -0
  19. data/lib/attest/execution_context.rb +156 -50
  20. data/lib/attest/expectation_result.rb +22 -1
  21. data/lib/attest/interface/output_writer_configurator.rb +25 -0
  22. data/lib/attest/interface/possible_tests_configurator.rb +35 -0
  23. data/lib/attest/interface/test_double_configurator.rb +40 -0
  24. data/lib/attest/output/basic_output_writer.rb +14 -46
  25. data/lib/attest/output/failures_only_output_writer.rb +47 -0
  26. data/lib/attest/output/output_writer.rb +124 -0
  27. data/lib/attest/output/output_writer_interface.rb +24 -0
  28. data/lib/attest/output/test_unit_output_writer.rb +32 -0
  29. data/lib/attest/proc/proc_source_reader.rb +49 -0
  30. data/lib/attest/rake/attesttask.rb +38 -0
  31. data/lib/attest/test_container.rb +15 -4
  32. data/lib/attest/test_loader.rb +28 -0
  33. data/lib/attest/test_object.rb +35 -29
  34. data/lib/attest/test_parser.rb +69 -11
  35. data/lib/trollop.rb +782 -0
  36. data/spec/interface/output_writer_configurator_test.rb +18 -0
  37. data/spec/interface/possible_tests_configurator_test.rb +55 -0
  38. data/spec/interface/test_double_configurator_test.rb +20 -0
  39. data/spec/output/output_writer_test.rb +20 -0
  40. data/spec/tmp/new_require_test.rb +10 -0
  41. metadata +55 -18
  42. data/.gitignore +0 -23
  43. data/examples/standard_calculator.rb +0 -28
  44. data/lib/attest/attest_error.rb +0 -7
  45. data/lib/attest/itself.rb +0 -34
@@ -0,0 +1,47 @@
1
+ require 'stringio'
2
+ require 'attest/output/output_writer'
3
+ require 'attest/expectation_result'
4
+
5
+ module Attest
6
+ module Output
7
+ class FailuresOnlyOutputWriter < Attest::Output::OutputWriter
8
+ def initialize
9
+ super()
10
+ @relevant_outputs = StringIO.new
11
+ end
12
+
13
+ def after_all_tests
14
+ super
15
+ @relevant_outputs.rewind
16
+ 2.times {puts}
17
+ puts @relevant_outputs.readlines
18
+ end
19
+
20
+ def before_container(container)
21
+ previous_container = @containers.last
22
+ @containers << container
23
+ end
24
+
25
+ def after_test(test_object)
26
+ relevant_result = determine_relevant_result test_object
27
+ if relevant_result && relevant_result.failure?
28
+ @relevant_outputs.puts "#{@containers.last.file}"
29
+ @relevant_outputs.puts " #{@containers.last.description}"
30
+ @relevant_outputs.puts " - #{test_object.description} [#{relevant_result.status.upcase}]"
31
+ @relevant_outputs.puts " #{relevant_result.source_location}"
32
+ @relevant_outputs.puts
33
+ elsif relevant_result && relevant_result.error?
34
+ e = relevant_result.attributes[:unexpected_error]
35
+ @relevant_outputs.puts "#{@containers.last.file}"
36
+ @relevant_outputs.puts " #{@containers.last.description}"
37
+ @relevant_outputs.puts " - #{test_object.description} [#{relevant_result.status.upcase}]"
38
+ @relevant_outputs.puts " #{e.class}: #{e.message}"
39
+ e.backtrace.each do |line|
40
+ @relevant_outputs.puts " #{line} "
41
+ end
42
+ @relevant_outputs.puts
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,124 @@
1
+ require 'attest/output/output_writer_interface'
2
+ require 'attest/expectation_result'
3
+
4
+ module Attest
5
+ module Output
6
+ class OutputWriter
7
+ include OutputWriterInterface
8
+ def initialize
9
+ self.instance_variable_set("@containers", [])
10
+ self.instance_variable_set("@start_time", nil)
11
+ self.instance_variable_set("@end_time", nil)
12
+ end
13
+
14
+ def before_all_tests
15
+ @start_time = Time.now
16
+ end
17
+ def after_all_tests
18
+ @end_time = Time.now
19
+ end
20
+ def before_container(container)
21
+ end
22
+ def after_container(container)
23
+ end
24
+ def before_test(test_object)
25
+ end
26
+ def after_test(test_object)
27
+ end
28
+ def summary
29
+ return unless @containers.size >= 1
30
+ expectation_status_hash = blank_status_hash
31
+ overall_test_status_hash = blank_status_hash
32
+ test_count = 0
33
+ @containers.each do |container|
34
+ container.test_objects.each do |test_object|
35
+ test_count += 1
36
+ current_test_statuses = determine_test_status test_object
37
+ overall_test_status_hash = merge_counting_hashes(overall_test_status_hash, current_test_statuses[0])
38
+ expectation_status_hash = merge_counting_hashes(expectation_status_hash, current_test_statuses[1])
39
+ end
40
+ end
41
+ puts
42
+ print "#{test_count} tests #{expectation_status_hash.inject(0){|sum, tuple| sum + tuple[1]}} expectations"
43
+ Attest::ExpectationResult.status_weights.sort{|a, b| a[1] <=> b[1]}.each {|status, weight| print " #{expectation_status_hash[status]} #{status.to_s}"}
44
+ puts
45
+ puts "Finished in #{elapsed_time(@end_time, @start_time)}"
46
+ end
47
+ def ignore_container(container)
48
+ if @containers.last.object_id == container.object_id
49
+ @containers.delete @containers.last
50
+ else
51
+ @containers.delete_if {|current_container| current_container.object_id == container.object_id}
52
+ end
53
+ end
54
+ def an_error(error_object)
55
+ puts "#{error_object.class}: #{error_object.message}"
56
+ error_object.backtrace.each do |line|
57
+ puts " #{line} "
58
+ end
59
+ end
60
+
61
+ private
62
+ def elapsed_time(end_time, start_time)
63
+ units = ["milliseconds", "seconds", "minutes", "hours"]
64
+ elapsed_seconds = end_time - start_time
65
+ if elapsed_seconds < 1
66
+ elapsed_time_as_string = "#{round_to(2, (elapsed_seconds * 1000))} #{units[0]}"
67
+ elsif elapsed_seconds >= 1 && elapsed_seconds < 60
68
+ elapsed_time_as_string = "#{round_to(2, elapsed_seconds)} #{units[1]}"
69
+ elsif elapsed_seconds >= 60 && elapsed_seconds < 3600
70
+ minsec = elapsed_seconds.divmod(60).collect{|num| round_to(2, num)}
71
+ elapsed_time_as_string = "#{minsec[0]} #{units[2]}, #{minsec[1]} #{units[1]}"
72
+ else
73
+ minsec = elapsed_seconds.divmod(60).collect{|num| round_to(2, num)}
74
+ hourminsec = minsec[0].divmod(60).collect{|num| round_to(2, num)}
75
+ hourminsec << minsec[1]
76
+ elapsed_time_as_string = "#{hourminsec[0]} #{units[3]}, #{hourminsec[1]} #{units[2]}, #{hourminsec[2]} #{units[1]}"
77
+ end
78
+ elapsed_time_as_string
79
+ end
80
+
81
+ def round_to(decimal_places, number)
82
+ rounded = (number * 10**decimal_places).round.to_f / 10**decimal_places
83
+ rounded_as_int = (rounded == rounded.to_i ? rounded.to_i : rounded)
84
+ rounded_as_int
85
+ end
86
+
87
+ def determine_relevant_result(test_object)
88
+ relevant_result = nil
89
+ test_object.results.each do |result|
90
+ relevant_result = result unless result.success?
91
+ end
92
+ relevant_result
93
+ end
94
+
95
+ def determine_test_status(test_object)
96
+ expectation_status_hash = blank_status_hash
97
+ overall_test_status_hash = blank_status_hash
98
+ dominant_result = nil
99
+ test_object.results.each do |result|
100
+ expectation_status_hash[result.status.to_sym] += 1
101
+ dominant_result = result if result > dominant_result
102
+ end
103
+ raise "Unexpected result status encountered! WTF!!!" if expectation_status_hash.keys.size > Attest::ExpectationResult.status_types.size
104
+ raise "Test without status encountered, all test should have a status!" unless dominant_result
105
+ overall_test_status_hash[dominant_result.status.to_sym] += 1
106
+ [overall_test_status_hash, expectation_status_hash]
107
+ end
108
+
109
+ def merge_counting_hashes(hash1, hash2)
110
+ hash1.inject(hash2) do |accumulator_hash, tuple|
111
+ accumulator_hash[tuple[0]] += tuple[1]
112
+ accumulator_hash
113
+ end
114
+ end
115
+
116
+ def blank_status_hash
117
+ Attest::ExpectationResult.status_types.inject({}) do |accumulator, status|
118
+ accumulator[status] = 0
119
+ accumulator
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,24 @@
1
+ module Attest
2
+ module Output
3
+ module OutputWriterInterface
4
+ def before_all_tests
5
+ end
6
+ def after_all_tests
7
+ end
8
+ def before_container(container)
9
+ end
10
+ def after_container(container)
11
+ end
12
+ def before_test(test_object)
13
+ end
14
+ def after_test(test_object)
15
+ end
16
+ def summary
17
+ end
18
+ def ignore_container(container)
19
+ end
20
+ def an_error(error_object)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ require 'attest/output/output_writer'
2
+ require 'attest/expectation_result'
3
+
4
+ module Attest
5
+ module Output
6
+ class TestUnitOutputWriter < Attest::Output::OutputWriter
7
+ def before_all_tests
8
+ super
9
+ puts
10
+ end
11
+
12
+ def after_all_tests
13
+ super
14
+ puts
15
+ end
16
+
17
+ def before_container(container)
18
+ previous_container = @containers.last
19
+ @containers << container
20
+ end
21
+
22
+ def after_test(test_object)
23
+ relevant_result = determine_relevant_result test_object
24
+ if relevant_result
25
+ print "#{relevant_result.status.upcase[0]}"
26
+ else
27
+ print '.'
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,49 @@
1
+ require 'stringio'
2
+ require 'irb/ruby-lex'
3
+
4
+ module Attest
5
+ class ProcSourceReader
6
+ def initialize(file, line)
7
+ @file = file
8
+ @start_line = line
9
+ end
10
+
11
+ def self.find(file, line)
12
+ source_reader = ProcSourceReader.new(file, line)
13
+ source_reader.read_source
14
+
15
+ end
16
+
17
+ def read_source
18
+ lines_starting_with_proc = read_lines_from_file
19
+ return nil if lines_starting_with_proc.nil?
20
+ lexer = RubyLex.new
21
+ lexer.set_input(StringIO.new(lines_starting_with_proc.join))
22
+ start_token, end_token = nil, nil
23
+ nesting = 0
24
+ while token = lexer.token
25
+ if RubyToken::TkDO === token || RubyToken::TkfLBRACE === token
26
+ nesting += 1
27
+ start_token = token if nesting == 1
28
+ elsif RubyToken::TkEND === token || RubyToken::TkRBRACE === token
29
+ if nesting == 1
30
+ end_token = token
31
+ break
32
+ end
33
+ nesting -= 1
34
+ end
35
+ end
36
+ proc_lines = lines_starting_with_proc[start_token.line_no - 1 .. end_token.line_no - 1]
37
+ proc_lines
38
+ end
39
+
40
+ def read_lines_from_file
41
+ raise "No file for proc where does it come from" unless @file
42
+ begin
43
+ File.readlines(@file)[(@start_line - 1) .. -1]
44
+ rescue
45
+ nil
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,38 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ module Rake
5
+ class AttestTask < TaskLib
6
+ attr_accessor :include, :exclude, :outputwriter, :testdouble
7
+ def initialize
8
+ @include = "attest/"
9
+ @exclude = nil
10
+ @outputwriter = "Basic"
11
+ @testdouble = "mocha"
12
+ yield self if block_given?
13
+ define
14
+ end
15
+
16
+ def define
17
+ desc "Run attest tests"
18
+ task :attest do
19
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../../lib/'))) unless $:.include?(File.expand_path(File.join(File.dirname(__FILE__), '../../../lib')))
20
+ require 'attest'
21
+ require 'attest/interface/output_writer_configurator'
22
+ require 'attest/interface/test_double_configurator'
23
+ require 'attest/interface/possible_tests_configurator'
24
+
25
+ Attest.configure do |config|
26
+ config.output_writer = Attest::OutputWriterConfigurator.configure(@outputwriter)
27
+ config.testdouble = Attest::TestDoubleConfigurator.configure(@testdouble)
28
+ config.possible_tests = Attest::PossibleTestsConfigurator.configure(@include, @exclude)
29
+ end
30
+
31
+ require 'attest/test_loader'
32
+
33
+ Attest::TestLoader.execute(Attest.config.possible_tests, Attest.config.output_writer)
34
+ end
35
+ self
36
+ end
37
+ end
38
+ end
@@ -1,7 +1,10 @@
1
+ require 'attest'
2
+
1
3
  module Attest
2
4
  class TestContainer
3
5
 
4
6
  attr_reader :description, :test_objects, :file
7
+ attr_accessor :before, :after
5
8
 
6
9
  def initialize(description)
7
10
  @file = Attest.current_file
@@ -14,11 +17,19 @@ module Attest
14
17
  end
15
18
 
16
19
  def execute_all
17
- Attest.output_writer.before_context(self)
18
- @test_objects.each do |test_object|
19
- test_object.run
20
+ Attest.output_writer.before_container(self)
21
+ container_context = Attest::ExecutionContext.new
22
+ begin
23
+ container_context.instance_eval(&@before) if @before
24
+ @test_objects.each do |test_object|
25
+ test_object.run container_context
26
+ end
27
+ container_context.instance_eval(&@after) if @after
28
+ rescue => e
29
+ Attest.output_writer.an_error(e)
30
+ Attest.output_writer.ignore_container(self)
20
31
  end
21
- Attest.output_writer.after_context
32
+ Attest.output_writer.after_container(self)
22
33
  end
23
34
  end
24
35
  end
@@ -0,0 +1,28 @@
1
+ require 'attest'
2
+ require 'attest/core_ext/kernel'
3
+
4
+ module Attest
5
+ class TestLoader
6
+ class << self
7
+ def execute(possible_tests, output_writer)
8
+ switch_on_attest_mode
9
+ output_writer.before_all_tests
10
+ possible_tests.each do |ruby_file|
11
+ Attest.config.current_file = ruby_file
12
+ load ruby_file
13
+ end
14
+ output_writer.after_all_tests
15
+ output_writer.summary
16
+ switch_off_attest_mode
17
+ end
18
+
19
+ def switch_on_attest_mode
20
+ ENV["attest"] = "true"
21
+ end
22
+
23
+ def switch_off_attest_mode
24
+ ENV["attest"] = nil
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,58 +1,64 @@
1
+ require 'attest'
2
+ require 'attest/execution_context'
3
+ require 'attest/expectation_result'
4
+
1
5
  module Attest
2
6
  class TestObject
3
7
  attr_reader :description, :results
4
- attr_accessor :nosetup
8
+ attr_accessor :nosetup, :disabled, :before, :after
5
9
  def initialize(description, test_block)
6
10
  @description = description
7
11
  @test_block = test_block
8
- @before = nil
9
- @after = nil
10
- @results = nil
11
- end
12
-
13
- def add_setup(block)
14
- @before = block
12
+ @results = []
15
13
  end
16
14
 
17
- def add_cleanup(block)
18
- @after = block
19
- end
20
-
21
- def run
15
+ def run(persistent_context)
22
16
  Attest.output_writer.before_test(self)
23
17
  error = nil
24
- context = Attest::ExecutionContext.new
18
+ context = Attest::ExecutionContext.new(persistent_context)
25
19
  begin
26
- Object.class_eval do
27
- define_method :itself do
28
- subject = self
29
- context.instance_eval {@subject = subject}
30
- context
31
- end
32
- end
33
- context.instance_eval(&@before) if @before && !nosetup
34
- context.instance_eval(&@test_block) if @test_block
35
- context.instance_eval(&@after) if @after && !nosetup
20
+ #Object.class_eval do
21
+ #define_method :itself do
22
+ #subject = self
23
+ #context.instance_eval {@subject = subject}
24
+ #context
25
+ #end
26
+ #end
27
+ context.instance_eval(&@before) if @before && !nosetup && !disabled
28
+ context.instance_eval(&@test_block) if @test_block && !disabled
29
+ context.instance_eval(&@after) if @after && !nosetup && !disabled
36
30
  rescue => e
37
31
  error = e
38
32
  ensure
39
33
  @results = context.results
40
34
  add_unexpected_error_result(error) if error
41
35
  add_pending_result unless @test_block
36
+ add_disabled_result if disabled
37
+ add_success_result if @results.size == 0
42
38
  end
43
39
  Attest.output_writer.after_test(self)
44
40
  end
45
41
 
46
42
  private
47
43
  def add_unexpected_error_result(error)
48
- result = Attest::ExpectationResult.new(:unexpected_error => error)
49
- result.error
50
- @results << result
44
+ create_and_add_result(:unexpected_error => error) {|result| result.error}
51
45
  end
52
46
 
53
47
  def add_pending_result
54
- result = Attest::ExpectationResult.new
55
- result.pending
48
+ create_and_add_result{|result| result.pending}
49
+ end
50
+
51
+ def add_disabled_result
52
+ create_and_add_result{|result| result.disabled}
53
+ end
54
+
55
+ def add_success_result
56
+ create_and_add_result{|result| result.success}
57
+ end
58
+
59
+ def create_and_add_result(opts={})
60
+ result = Attest::ExpectationResult.new(opts)
61
+ yield result
56
62
  @results << result
57
63
  end
58
64
  end