dtest 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/LICENSE.txt +202 -0
- data/README.rdoc +26 -0
- data/Rakefile +21 -0
- data/VERSION +1 -0
- data/bin/dtest +4 -0
- data/dtest.gemspec +34 -0
- data/examples/sample.rb +130 -0
- data/lib/dtest.rb +0 -0
- data/lib/dtest/command.rb +33 -0
- data/lib/dtest/core.rb +257 -0
- data/lib/dtest/dsl.rb +19 -0
- data/lib/dtest/failure.rb +27 -0
- data/lib/dtest/global.rb +91 -0
- data/lib/dtest/progress.rb +166 -0
- data/lib/dtest/report.rb +121 -0
- data/lib/dtest/result.rb +189 -0
- data/lib/dtest/runner.rb +22 -0
- data/lib/dtest/test.rb +212 -0
- data/lib/dtest/util.rb +43 -0
- data/lib/dtest/version.rb +5 -0
- data/spec/test_abort_spec.rb +772 -0
- data/spec/test_assert_spec.rb +146 -0
- data/spec/test_exception_spec.rb +303 -0
- data/spec/test_expect_spec.rb +288 -0
- data/spec/test_spec.rb +272 -0
- data/spec/test_value_parameterized_spec.rb +46 -0
- metadata +101 -0
data/lib/dtest/core.rb
ADDED
@@ -0,0 +1,257 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'time'
|
5
|
+
require 'rexml/document'
|
6
|
+
require 'rexml/cdata'
|
7
|
+
|
8
|
+
require 'dtest/failure'
|
9
|
+
require 'dtest/util'
|
10
|
+
|
11
|
+
require 'dtest/result'
|
12
|
+
require 'dtest/test'
|
13
|
+
require 'dtest/global'
|
14
|
+
|
15
|
+
|
16
|
+
module DTest
|
17
|
+
|
18
|
+
class Abort < Exception
|
19
|
+
end
|
20
|
+
|
21
|
+
class AbortTest < Abort
|
22
|
+
def to_s
|
23
|
+
"AbortTest #{super}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class AbortTestCase < Abort
|
28
|
+
def to_s
|
29
|
+
"AbortTestCase #{super}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class AbortGlobal < Abort
|
34
|
+
def to_s
|
35
|
+
"AbortGlobal #{super}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Context
|
40
|
+
def initialize(let = nil)
|
41
|
+
# テスト記述側(ブロック)から__stateが参照されないのが前提
|
42
|
+
@__state = {:let => let}
|
43
|
+
end
|
44
|
+
|
45
|
+
def call(state, block)
|
46
|
+
@__state = @__state.merge(state)
|
47
|
+
begin
|
48
|
+
instance_eval(&block)
|
49
|
+
rescue AbortTest, AbortTestCase, AbortGlobal => e
|
50
|
+
# スルー
|
51
|
+
raise e
|
52
|
+
rescue StandardError, Exception => e
|
53
|
+
# ブロック内の例外はabortとして処理する
|
54
|
+
catch_exception(e)
|
55
|
+
abort_assert
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def set(name, val)
|
60
|
+
return if @__state[:let] == nil
|
61
|
+
|
62
|
+
# set variable
|
63
|
+
@__state[:let].instance_variable_set("@#{name}", val)
|
64
|
+
# define getter method
|
65
|
+
@__state[:let].instance_eval <<-EOS
|
66
|
+
def #{name}
|
67
|
+
@#{name}
|
68
|
+
end
|
69
|
+
EOS
|
70
|
+
end
|
71
|
+
|
72
|
+
# :let value getter
|
73
|
+
def method_missing(name, *args, &block)
|
74
|
+
if @__state[:let] && @__state[:let].public_methods.map(&:to_sym).include?(name)
|
75
|
+
# getter value
|
76
|
+
@__state[:let].send(name)
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# value-parameterized test parameters
|
83
|
+
def param
|
84
|
+
@__state[:parameter]
|
85
|
+
end
|
86
|
+
|
87
|
+
# abort type when assertion failed
|
88
|
+
def assert_failure?
|
89
|
+
@__state[:option][:assert_abort]
|
90
|
+
end
|
91
|
+
|
92
|
+
public
|
93
|
+
# expect/assert
|
94
|
+
def expect_true(condition, message = nil)
|
95
|
+
failed_true(message) unless condition
|
96
|
+
end
|
97
|
+
|
98
|
+
def expect_false(condition, message = nil)
|
99
|
+
failed_false(message) if condition
|
100
|
+
end
|
101
|
+
|
102
|
+
def expect_equal(expected, actual, message = nil)
|
103
|
+
failed_equal(expected, actual, message) unless expected == actual
|
104
|
+
end
|
105
|
+
|
106
|
+
def expect_not_equal(expected, actual, message = nil)
|
107
|
+
failed_equal(expected, actual, message) if expected == actual
|
108
|
+
end
|
109
|
+
|
110
|
+
def assert_true(condition, message = nil)
|
111
|
+
unless condition
|
112
|
+
failed_true(message)
|
113
|
+
abort_assert
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def assert_false(condition, message = nil)
|
118
|
+
if condition
|
119
|
+
failed_false(message)
|
120
|
+
abort_assert
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def assert_equal(expected, actual, message = nil)
|
125
|
+
unless expected == actual
|
126
|
+
failed_equal(expected, actual, message)
|
127
|
+
abort_assert
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def assert_not_equal(expected, actual, message = nil)
|
132
|
+
if expected == actual
|
133
|
+
failed_equal(expected, actual, message)
|
134
|
+
abort_assert
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def assert_error(*errors, &block)
|
139
|
+
begin
|
140
|
+
block.call
|
141
|
+
rescue *errors => actual_error
|
142
|
+
raised_expected_error = true
|
143
|
+
rescue RuntimeError, Exception => actual_error
|
144
|
+
raised_expected_error = false
|
145
|
+
else
|
146
|
+
str = "exception expected but none was thrown\n"
|
147
|
+
failed(str)
|
148
|
+
return
|
149
|
+
end
|
150
|
+
|
151
|
+
unless raised_expected_error
|
152
|
+
str = "exception expected #{errors.to_s} but #{actual_error.inspect}\n"
|
153
|
+
failed(str)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# abort
|
158
|
+
def abort_if(condition, message = nil)
|
159
|
+
str = "Abort"
|
160
|
+
str += ": #{message}\n" if message
|
161
|
+
failed(str)
|
162
|
+
raise AbortTest.new(str) if condition
|
163
|
+
end
|
164
|
+
|
165
|
+
def abort_case_if(condition, message = nil)
|
166
|
+
str = "Abort TestCase"
|
167
|
+
str += ": #{message}\n" if message
|
168
|
+
failed(str)
|
169
|
+
raise AbortTestCase.new(str) if condition
|
170
|
+
end
|
171
|
+
|
172
|
+
def abort_global_if(condition, message = nil)
|
173
|
+
str = "Abort global"
|
174
|
+
str += ": #{message}\n" if message
|
175
|
+
failed(str)
|
176
|
+
raise AbortGlobal.new(str) if condition
|
177
|
+
end
|
178
|
+
|
179
|
+
private #internal methods
|
180
|
+
def failed_equal(expected, actual, message = nil)
|
181
|
+
str = <<END
|
182
|
+
expected: #{expected.inspect}
|
183
|
+
got: #{actual.inspect}
|
184
|
+
END
|
185
|
+
str += "#{message}\n" if message
|
186
|
+
failed(str, 3)
|
187
|
+
end
|
188
|
+
|
189
|
+
def failed_true(message = nil)
|
190
|
+
str = "condition must be true\n"
|
191
|
+
str += "#{message}\n" if message
|
192
|
+
failed(str, 3)
|
193
|
+
end
|
194
|
+
|
195
|
+
def failed_false(message = nil)
|
196
|
+
str = "condition must be false\n"
|
197
|
+
str += "#{message}\n" if message
|
198
|
+
failed(str, 3)
|
199
|
+
end
|
200
|
+
|
201
|
+
def catch_exception(e)
|
202
|
+
str = "Exception #{e.inspect}\n"
|
203
|
+
add_failure(str, e.backtrace.first)
|
204
|
+
end
|
205
|
+
|
206
|
+
def add_failure(error, backtrace)
|
207
|
+
if @__state[:result]
|
208
|
+
@__state[:result] << (Test::FailureMessage.new(@__state[:parent], @__state[:name], error, backtrace))
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# push failure message
|
213
|
+
def failed(error, level = 2)
|
214
|
+
add_failure(error, caller(level).first)
|
215
|
+
end
|
216
|
+
|
217
|
+
def abort_assert
|
218
|
+
case assert_failure?
|
219
|
+
when :global
|
220
|
+
raise AbortGlobal.new
|
221
|
+
when :testcase
|
222
|
+
raise AbortTestCase.new
|
223
|
+
else
|
224
|
+
raise AbortTest.new
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end # class Context
|
228
|
+
|
229
|
+
|
230
|
+
class Block
|
231
|
+
attr_reader :name
|
232
|
+
attr_accessor :parent, :result, :parameter
|
233
|
+
|
234
|
+
def initialize(name, option, &block)
|
235
|
+
@name = name
|
236
|
+
@parent = nil
|
237
|
+
@option = option
|
238
|
+
@block = block
|
239
|
+
@result = nil # caller result object
|
240
|
+
@parameter = nil
|
241
|
+
end
|
242
|
+
|
243
|
+
def call(context, name = nil)
|
244
|
+
@parent = name if name
|
245
|
+
context.call({
|
246
|
+
:parent => @parent,
|
247
|
+
:name => @name,
|
248
|
+
:option => @option,
|
249
|
+
:result => @result,
|
250
|
+
:parameter => @parameter,
|
251
|
+
}, @block)
|
252
|
+
end
|
253
|
+
|
254
|
+
end # class Context
|
255
|
+
|
256
|
+
end # module DTest
|
257
|
+
|
data/lib/dtest/dsl.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
require 'dtest/core'
|
3
|
+
|
4
|
+
module DTest
|
5
|
+
module DSL
|
6
|
+
def TestCase(name, options = {}, &block)
|
7
|
+
manager = Test::Manager::instance
|
8
|
+
manager.instance_eval(&block)
|
9
|
+
manager.add(name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def GlobalHarness(&block)
|
13
|
+
manager = Global::Manager::instance
|
14
|
+
manager.instance_eval(&block)
|
15
|
+
end
|
16
|
+
end # module DSL
|
17
|
+
end # module DTest
|
18
|
+
|
19
|
+
include DTest::DSL
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
module DTest
|
3
|
+
def self.parse_caller(at)
|
4
|
+
if /^(.+?):(\d+)(?::in `(.*)')?/ =~ at
|
5
|
+
file = $1
|
6
|
+
line = $2.to_i
|
7
|
+
method = $3
|
8
|
+
[file, line, method]
|
9
|
+
else
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.failure_line(backtrace)
|
15
|
+
file, line, method = parse_caller(backtrace)
|
16
|
+
if file && line && File.exists?(file)
|
17
|
+
[file, line, File.readlines(file)[line - 1].strip]
|
18
|
+
else
|
19
|
+
[file, line, "Unable to find #{file} to read failed line"]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.failure_caller(level)
|
24
|
+
failure_line(caller(level).first)
|
25
|
+
end
|
26
|
+
end # DTest
|
27
|
+
|
data/lib/dtest/global.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
|
2
|
+
module DTest
|
3
|
+
module Global
|
4
|
+
class Harness
|
5
|
+
include Hook
|
6
|
+
|
7
|
+
attr_accessor :global
|
8
|
+
attr_accessor :before, :after
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@before = []
|
12
|
+
@after = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute_after(list, context)
|
16
|
+
begin
|
17
|
+
exec(list, context)
|
18
|
+
rescue StandardError, Exception => e
|
19
|
+
# にぎりつぶす
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def start(testcases)
|
24
|
+
# Progress
|
25
|
+
context = Context.new
|
26
|
+
Progress.setUpGlobal(testcases)
|
27
|
+
|
28
|
+
global_result = Test::GlobalResult.new(testcases)
|
29
|
+
|
30
|
+
@before.each {|b| b.result = global_result.before_failure }
|
31
|
+
@after.each {|b| b.result = global_result.after_failure }
|
32
|
+
|
33
|
+
global_result.timer {
|
34
|
+
begin
|
35
|
+
# execute before
|
36
|
+
exec(@before, context)
|
37
|
+
|
38
|
+
# execute cases
|
39
|
+
testcases.each do |testcase|
|
40
|
+
execute_testcase(global_result, testcase)
|
41
|
+
end
|
42
|
+
rescue AbortGlobal => e
|
43
|
+
# finish
|
44
|
+
rescue StandardError, Exception => e
|
45
|
+
# Blockでエラー処理しているので、にぎりつぶす
|
46
|
+
ensure
|
47
|
+
execute_after(@after, context)
|
48
|
+
Progress.tearDownGlobal
|
49
|
+
end
|
50
|
+
}
|
51
|
+
|
52
|
+
global_result
|
53
|
+
end
|
54
|
+
|
55
|
+
def execute_testcase(global_result, testcase)
|
56
|
+
begin
|
57
|
+
# execute TestCases
|
58
|
+
testcase.execute(global_result)
|
59
|
+
rescue AbortTest, AbortTestCase => e
|
60
|
+
# にぎりつぶす
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end # class Harness
|
64
|
+
|
65
|
+
class Manager
|
66
|
+
include Singleton
|
67
|
+
attr_accessor :harness
|
68
|
+
|
69
|
+
def initialize
|
70
|
+
clear
|
71
|
+
end
|
72
|
+
|
73
|
+
def clear
|
74
|
+
remove_instance_var
|
75
|
+
@harness = Harness.new
|
76
|
+
end
|
77
|
+
|
78
|
+
def before(option = {}, &block)
|
79
|
+
b = Block.new("before", option, &block)
|
80
|
+
b.parent = 'Global'
|
81
|
+
@harness.before << b
|
82
|
+
end
|
83
|
+
|
84
|
+
def after(option = {}, &block)
|
85
|
+
b = Block.new("after", option, &block)
|
86
|
+
b.parent = 'Global'
|
87
|
+
@harness.after << b
|
88
|
+
end
|
89
|
+
end # class Manager
|
90
|
+
end # module Global
|
91
|
+
end # module DTest
|
@@ -0,0 +1,166 @@
|
|
1
|
+
|
2
|
+
require 'dtest/report'
|
3
|
+
|
4
|
+
module DTest
|
5
|
+
class Progress
|
6
|
+
def self.test_str(test_size)
|
7
|
+
test_str = test_size > 1 ? 'tests' : 'test'
|
8
|
+
"#{test_size} #{test_str}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.case_str(testcase_size)
|
12
|
+
testcase_str = testcase_size > 1 ? 'cases' : 'case'
|
13
|
+
"#{testcase_size} #{testcase_str}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.error_str(size)
|
17
|
+
str = 'error'
|
18
|
+
str += 's' if size > 0
|
19
|
+
"#{size} #{str}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.setUpGlobal(testcase)
|
23
|
+
test_size = testcase.inject(0) { |sum, t| sum += t.test.size}
|
24
|
+
Report.tag :global, test_str(test_size) + " from " + case_str(testcase.size)
|
25
|
+
Report.tag :global, "Global test environment set-up."
|
26
|
+
puts ''
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.tearDownGlobal
|
30
|
+
Report.tag :global, "Global test environment tear-down."
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.setUpTestCase(name, size)
|
34
|
+
Report.tag :line, test_str(size) + " from #{name}"
|
35
|
+
Report.tag :testcase, "#{name} set-up."
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.tearDownTestCase(name, size, elapsed)
|
39
|
+
Report.tag :testcase, "#{name} tear-down."
|
40
|
+
Report.tag :line, test_str(size) + " executed from #{name} (#{elapsed} seconds)"
|
41
|
+
puts ''
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.test(casename, name)
|
45
|
+
Report.left :run, "#{casename}.#{name}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.test_success(casename, name)
|
49
|
+
Report.right :ok, "#{casename}.#{name}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.test_fail(casename, name)
|
53
|
+
Report.right :fail, "#{casename}.#{name}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.print_result(gresult)
|
57
|
+
# collect TestCase failures
|
58
|
+
cases = []
|
59
|
+
gresult.result.each do |result|
|
60
|
+
# test
|
61
|
+
test = []
|
62
|
+
result.result.each do |r|
|
63
|
+
unless r.empty? && r.ba_empty?
|
64
|
+
test << r
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
unless result.ba_empty? && test.empty?
|
69
|
+
cases << {
|
70
|
+
:case => result,
|
71
|
+
:test => test,
|
72
|
+
}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
global_failed = !gresult.ba_empty?
|
77
|
+
|
78
|
+
#####################
|
79
|
+
# Output error status
|
80
|
+
split = global_failed || !cases.empty?
|
81
|
+
Report.split if split
|
82
|
+
|
83
|
+
# Report Global
|
84
|
+
if global_failed
|
85
|
+
err = []
|
86
|
+
err << 'before' unless gresult.before_failure.empty?
|
87
|
+
err << 'after' unless gresult.after_failure.empty?
|
88
|
+
Report.tag :global, "Global failure"
|
89
|
+
Report.tag :empty, " " + err.join(', ')
|
90
|
+
end
|
91
|
+
|
92
|
+
# Report TestCase
|
93
|
+
unless cases.empty?
|
94
|
+
str = cases.inject([]) {|a, s| a << s[:case].name }.join(', ')
|
95
|
+
Report.tag :testcase, "#{case_str(cases.size)}"
|
96
|
+
Report.tag :empty, " #{str}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Report test each cases
|
100
|
+
cases.each do |x|
|
101
|
+
c = x[:case]
|
102
|
+
t = x[:test]
|
103
|
+
err = []
|
104
|
+
err << 'beforeCase' unless c.before_failure.empty?
|
105
|
+
err << 'afterCase' unless c.after_failure.empty?
|
106
|
+
err = t.inject(err) {|a, s| a << s.name }
|
107
|
+
Report.tag :test, "#{c.name}: #{error_str(err.size)}"
|
108
|
+
Report.tag :empty, " #{err.join(', ')}"
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
##################
|
114
|
+
# Output failures
|
115
|
+
Report.split if split
|
116
|
+
|
117
|
+
# Global before/after failure
|
118
|
+
if global_failed
|
119
|
+
# before
|
120
|
+
Report.tag :fail, 'Global.before' unless gresult.before_failure.empty?
|
121
|
+
print_failure(gresult.before_failure)
|
122
|
+
|
123
|
+
# after
|
124
|
+
Report.tag :fail, 'Global.after' unless gresult.after_failure.empty?
|
125
|
+
print_failure(gresult.after_failure)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Report testcase and test failures
|
129
|
+
cases.each do |x|
|
130
|
+
c = x[:case]
|
131
|
+
casename = c.name
|
132
|
+
#Report.tag :testcase, "#{casename}"
|
133
|
+
# testcase before/after
|
134
|
+
Report.tag :fail, "#{casename}.beforeCase" unless c.before_failure.empty?
|
135
|
+
print_failure(c.before_failure)
|
136
|
+
Report.tag :fail, "#{casename}.afterCase" unless c.after_failure.empty?
|
137
|
+
print_failure(c.after_failure)
|
138
|
+
|
139
|
+
# test before/test/after
|
140
|
+
x[:test].each do |t|
|
141
|
+
name = t.name
|
142
|
+
Report.tag :fail, "#{casename}.#{name}.before" unless t.before_failure.empty?
|
143
|
+
print_failure(t.before_failure)
|
144
|
+
Report.tag :fail, "#{casename}.#{name}" unless t.failure.empty?
|
145
|
+
print_failure(t)
|
146
|
+
Report.tag :fail, "#{casename}.#{name}.after" unless t.after_failure.empty?
|
147
|
+
print_failure(t.after_failure)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
puts ""
|
152
|
+
puts "Finished in #{gresult.elapsed} seconds"
|
153
|
+
puts "--------------------------------"
|
154
|
+
Report.tag :passed, gresult.passed
|
155
|
+
Report.tag :failed, gresult.failed
|
156
|
+
Report.tag :tested, gresult.executed
|
157
|
+
Report.tag :untest, gresult.untested
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.print_failure(failure)
|
161
|
+
failure.failure.each do |msg|
|
162
|
+
msg.print
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end # class Progress
|
166
|
+
end # module DTest
|