attest 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Gemfile.lock +6 -1
- data/README.rdoc +121 -36
- data/Rakefile +12 -1
- data/VERSION +1 -1
- data/attest.gemspec +61 -36
- data/bin/attest +18 -24
- data/doodle.txt +73 -29
- data/examples/{magic_calculator.rb → basic_functionality_example.rb} +42 -8
- data/examples/mocha_example.rb +43 -0
- data/examples/module_example.rb +49 -0
- data/examples/more/{placeholder.rb → multiple_context_example.rb} +0 -0
- data/examples/more/nesting/expectations_as_tests_example.rb +20 -0
- data/lib/attest.rb +4 -20
- data/lib/attest/config.rb +3 -6
- data/lib/attest/core_ext/kernel.rb +19 -1
- data/lib/attest/core_ext/object.rb +1 -2
- data/lib/attest/core_ext/proc.rb +35 -0
- data/lib/attest/execution_context.rb +156 -50
- data/lib/attest/expectation_result.rb +22 -1
- data/lib/attest/interface/output_writer_configurator.rb +25 -0
- data/lib/attest/interface/possible_tests_configurator.rb +35 -0
- data/lib/attest/interface/test_double_configurator.rb +40 -0
- data/lib/attest/output/basic_output_writer.rb +14 -46
- data/lib/attest/output/failures_only_output_writer.rb +47 -0
- data/lib/attest/output/output_writer.rb +124 -0
- data/lib/attest/output/output_writer_interface.rb +24 -0
- data/lib/attest/output/test_unit_output_writer.rb +32 -0
- data/lib/attest/proc/proc_source_reader.rb +49 -0
- data/lib/attest/rake/attesttask.rb +38 -0
- data/lib/attest/test_container.rb +15 -4
- data/lib/attest/test_loader.rb +28 -0
- data/lib/attest/test_object.rb +35 -29
- data/lib/attest/test_parser.rb +69 -11
- data/lib/trollop.rb +782 -0
- data/spec/interface/output_writer_configurator_test.rb +18 -0
- data/spec/interface/possible_tests_configurator_test.rb +55 -0
- data/spec/interface/test_double_configurator_test.rb +20 -0
- data/spec/output/output_writer_test.rb +20 -0
- data/spec/tmp/new_require_test.rb +10 -0
- metadata +55 -18
- data/.gitignore +0 -23
- data/examples/standard_calculator.rb +0 -28
- data/lib/attest/attest_error.rb +0 -7
- 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.
|
18
|
-
|
19
|
-
|
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.
|
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
|
data/lib/attest/test_object.rb
CHANGED
@@ -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
|
-
@
|
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
|
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
|
-
|
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
|
55
|
-
|
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
|