dtest 0.0.1
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 +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
|