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.
- 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
|