dtest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,121 @@
1
+
2
+ module DTest
3
+ class Report
4
+ private
5
+ TAG_BASE = [
6
+ 'Global', 'TestCase', 'Test', 'RUN', 'OK', 'FAIL',
7
+ 'PASSED', 'FAILED', 'TESTED', 'UNTEST',
8
+ ]
9
+ TAG_S = TAG_BASE.map {|t| " #{t} "}
10
+ MAX_LEN = TAG_S.map {|s| s.size}.max
11
+
12
+ TAG_MAP = Hash[[TAG_BASE.map {|t| t.downcase.to_sym}, TAG_S].transpose].merge({
13
+ :line => '-' * MAX_LEN,
14
+ :empty => ''
15
+ })
16
+
17
+ TAGS_COLOR = {
18
+ :global => :blue,
19
+ :testcase => :blue,
20
+ :test => :blue,
21
+
22
+ :run => :yellow,
23
+ :fail => :red,
24
+ :ok => :green,
25
+
26
+ :passed => :green,
27
+ :failed => :green,
28
+ :tested => :green,
29
+ :untest => :green,
30
+ }
31
+
32
+ # symbol to string
33
+ def self.tag_s(tag)
34
+ if TAG_MAP.include?(tag)
35
+ text = TAG_MAP[tag]
36
+ else
37
+ text = tag.to_s
38
+ end
39
+ end
40
+
41
+ def self.colored_tag(tag)
42
+ text = tag_s(tag)
43
+
44
+ if TAGS_COLOR.include?(tag)
45
+ send(TAGS_COLOR[tag], text)
46
+ else
47
+ text
48
+ end
49
+ end
50
+
51
+ def self.space(size)
52
+ if size > 0
53
+ ' ' * size
54
+ else
55
+ ''
56
+ end
57
+ end
58
+
59
+ def self.s_center(tag)
60
+ len = MAX_LEN - tag_s(tag).size
61
+ left = len / 2
62
+ right = len / 2 + (len.odd? ? 1 : 0)
63
+ space(left) + colored_tag(tag) + space(right)
64
+ end
65
+
66
+ def self.s_left(tag)
67
+ colored_tag(tag) + space(MAX_LEN - tag_s(tag).size)
68
+ end
69
+
70
+ def self.s_right(tag)
71
+ space(MAX_LEN - tag_s(tag).size) + colored_tag(tag)
72
+ end
73
+
74
+ def self.colorize(text, code)
75
+ if @color_enabled
76
+ "#{code}#{text}\e[0m"
77
+ else
78
+ text
79
+ end
80
+ end
81
+
82
+ def self.red(text)
83
+ colorize(text, "\e[31m")
84
+ end
85
+
86
+ def self.green(text)
87
+ colorize(text, "\e[32m")
88
+ end
89
+
90
+ def self.yellow(text)
91
+ colorize(text, "\e[33m")
92
+ end
93
+
94
+ def self.blue(text)
95
+ colorize(text, "\e[34m")
96
+ end
97
+
98
+ public
99
+ def self.color_enabled=(t)
100
+ @color_enabled = t
101
+ end
102
+
103
+ def self.split(count = 30)
104
+ puts ''
105
+ puts '-' * count
106
+ end
107
+
108
+ def self.tag(s, text = '')
109
+ puts "[#{s_center(s)}] #{text}"
110
+ end
111
+
112
+ def self.left(s, text = '')
113
+ puts "[#{s_left(s)}] #{text}"
114
+ end
115
+
116
+ def self.right(s, text = '')
117
+ puts "[#{s_right(s)}] #{text}"
118
+ end
119
+
120
+ end # class Report
121
+ end # module DTest
@@ -0,0 +1,189 @@
1
+ require 'dtest/failure'
2
+
3
+ module DTest
4
+
5
+ module Test
6
+ class Failure
7
+ def initialize
8
+ @failure = []
9
+ end
10
+
11
+ def <<(s)
12
+ @failure << s
13
+ end
14
+
15
+ def empty?
16
+ @failure.empty?
17
+ end
18
+
19
+ def failure
20
+ @failure
21
+ end
22
+ end
23
+
24
+ # before/after result
25
+ module BAResult
26
+ attr_accessor :before_failure, :after_failure
27
+
28
+ def initialize
29
+ super
30
+ @before_failure = Failure.new
31
+ @after_failure = Failure.new
32
+ end
33
+
34
+ def ba_empty?
35
+ @before_failure.empty? && @after_failure.empty?
36
+ end
37
+ end #module BAResult
38
+
39
+ class FailureMessage
40
+ attr_accessor :parent, :name
41
+ attr_accessor :file, :line, :error_line
42
+
43
+ def initialize(parent, name, message, backtrace)
44
+ @parent = parent
45
+ @nane = name
46
+ @message = message
47
+
48
+ @file, @line, @error_line = DTest::failure_line(backtrace)
49
+ end
50
+
51
+ def location
52
+ if file && line
53
+ "#{file}:#{line}\n"
54
+ else
55
+ ""
56
+ end
57
+ end
58
+
59
+ def all
60
+ location + @message
61
+ end
62
+
63
+ def print
64
+ #str += "[#{parent}]" if parent
65
+ #str += " '#{name}'\n" if name
66
+ str = @message
67
+ str += " Failure/Error: #{error_line}\n" if error_line
68
+ str += " # #{file}:#{line}\n" if file && line
69
+ puts "#{str}\n"
70
+ end
71
+ end # FailureMessage
72
+
73
+
74
+ class Result < Failure
75
+ include Stopwatch
76
+ include BAResult
77
+
78
+ PASS = 'Pass'
79
+ FAIL = 'Fail'
80
+ UNTEST = 'Untested'
81
+
82
+ attr_accessor :name, :result
83
+
84
+ def initialize(name)
85
+ super()
86
+ @name = name
87
+ @result = FAIL
88
+ end
89
+ end # class Result
90
+
91
+ class CaseResult
92
+ include Stopwatch
93
+ include BAResult
94
+
95
+ attr_accessor :name
96
+ attr_accessor :result
97
+ attr_accessor :passed, :failed, :executed, :untested
98
+
99
+ def initialize(name)
100
+ super()
101
+ @name = name
102
+ @passed = 0
103
+ @failed = 0
104
+ @executed = 0
105
+ @untested = 0
106
+ # list of Result
107
+ @result = []
108
+ end
109
+
110
+ def add(result)
111
+ @result << result
112
+ end
113
+ end # class CaseResult
114
+
115
+ class GlobalResult
116
+ include Stopwatch
117
+ include BAResult
118
+
119
+ attr_accessor :result
120
+
121
+ def initialize(testcases)
122
+ super()
123
+ @result = []
124
+ @test_size = testcases.inject(0) { |sum, t| sum += t.test.size}
125
+ end
126
+
127
+ def add(res)
128
+ @result << res
129
+ end
130
+
131
+ def passed
132
+ @passed = result.inject(0) {|sum, r| sum += r.passed} unless @passed
133
+ @passed
134
+ end
135
+
136
+ def failed
137
+ @failed ||= result.inject(0) {|sum, r| sum += r.failed}
138
+ end
139
+
140
+
141
+ def executed
142
+ @executed ||= result.inject(0) {|sum, r| sum += r.executed}
143
+ end
144
+
145
+ def untested
146
+ @untested ||= @test_size - executed
147
+ end
148
+
149
+ def outputxml(output_path)
150
+ doc = REXML::Document.new
151
+ root = doc.add_element('testsuites', {
152
+ 'name' => 'Global',
153
+ 'tests' => executed,
154
+ 'failures' => failed,
155
+ 'errors' => 0,
156
+ 'time' => elapsed,
157
+ })
158
+
159
+ result.each do |result|
160
+ suite = root.add_element('testsuite', {
161
+ 'name' => result.name,
162
+ 'tests' => result.executed,
163
+ 'failures' => result.failed,
164
+ 'errors' => 0,
165
+ 'time' => result.elapsed,
166
+ })
167
+
168
+ result.result.each do |t|
169
+ test = suite.add_element('testcase', {
170
+ 'name' => t.name,
171
+ 'status' => 'run',
172
+ 'classname' => result.name,
173
+ 'time' => t.elapsed,
174
+ })
175
+ t.failure.each do |msg|
176
+ failure = test.add_element('failure', {
177
+ 'type' => '',
178
+ })
179
+ failure.text = REXML::CData.new(msg.all)
180
+ end
181
+ end
182
+ end
183
+
184
+ doc.write(REXML::Output.new(File.new(output_path, 'w+'), REXML::Encoding::UTF_8))
185
+ end
186
+ end # class GlobalResult
187
+
188
+ end # module Test
189
+ end # module DTest
@@ -0,0 +1,22 @@
1
+
2
+
3
+ require 'dtest/core'
4
+ require 'dtest/progress'
5
+
6
+ module DTest
7
+ class Runner
8
+ def self.run(files)
9
+ files.each do |file|
10
+ load file
11
+ end
12
+
13
+ test = Test::Manager::instance.cases
14
+ Global::Manager::instance.harness.start(test)
15
+ end
16
+
17
+ def self.report(gresult)
18
+ Progress.print_result(gresult)
19
+ end
20
+
21
+ end # class Runner
22
+ end # module DTest
data/lib/dtest/test.rb ADDED
@@ -0,0 +1,212 @@
1
+
2
+ require 'dtest/progress'
3
+
4
+ module DTest
5
+ module Test
6
+ class Case
7
+ include Hook
8
+
9
+ attr_accessor :name
10
+ attr_accessor :beforeCase, :afterCase
11
+ attr_accessor :before, :after
12
+ attr_accessor :test
13
+
14
+ def initialize(name, beforeCase, afterCase, before, after, test)
15
+ @name = name
16
+ @beforeCase = beforeCase
17
+ @afterCase = afterCase
18
+ @before = before
19
+ @after = after
20
+ @test = test
21
+ @defined_values = Object.new
22
+ end
23
+
24
+ private
25
+ def execute_after_case(list, context)
26
+ begin
27
+ exec(list, context)
28
+ rescue AbortTest, AbortTestCase
29
+ # にぎりつぶす
30
+ end
31
+ end
32
+
33
+ # execute before/after
34
+ def execute_after(list, context)
35
+ begin
36
+ exec(list, context)
37
+ rescue AbortTest
38
+ # にぎりつぶす
39
+ end
40
+ end
41
+
42
+ public
43
+ def execute(global_result)
44
+ # TestCase result
45
+ caseresult = CaseResult.new(@name)
46
+ global_result.add(caseresult)
47
+
48
+ # set result
49
+ @beforeCase.each {|b| b.result = caseresult.before_failure }
50
+ @afterCase.each {|b| b.result = caseresult.after_failure }
51
+
52
+ Progress.setUpTestCase(name, @test.size)
53
+ executed = 0
54
+ passed = 0
55
+ context = Context.new(@defined_values)
56
+
57
+ begin
58
+ caseresult.timer {
59
+ # execute beforeCase
60
+ exec(@beforeCase, context)
61
+
62
+ # execute each test
63
+ @test.each do |test|
64
+ executed += 1
65
+ result = Result.new(test.name)
66
+ caseresult.add(result)
67
+ execute_test(result, test)
68
+ passed += 1 if result.result == Result::PASS
69
+ end
70
+ } # Stopwatch::timer
71
+ rescue AbortTestCase
72
+ # にぎりつぶす
73
+ ensure
74
+ # report
75
+ caseresult.passed = passed
76
+ caseresult.failed = executed - passed
77
+ caseresult.executed = executed
78
+ caseresult.untested = @test.size - executed
79
+
80
+ # execute afterCase
81
+ begin
82
+ execute_after_case(@afterCase, context)
83
+ ensure
84
+ # report testcase finished
85
+ Progress.tearDownTestCase(name, executed, caseresult.elapsed)
86
+ end
87
+ end
88
+ end
89
+
90
+ def execute_test(result, test)
91
+ Progress.test(@name, test.name)
92
+
93
+ context = Context.new(@defined_values)
94
+
95
+ # set result
96
+ @before.each {|b| b.result = result.before_failure }
97
+ @after.each {|b| b.result = result.after_failure }
98
+
99
+ begin
100
+ # execute before blocks
101
+ exec(@before, context)
102
+
103
+ # exeucte test
104
+ result.timer {
105
+ test.result = result
106
+ test.call(context, name)
107
+ }
108
+ rescue AbortTest
109
+ # にぎりつぶす
110
+ # 次のテストを実行する
111
+ rescue AbortTestCase, AbortGlobal => e
112
+ # スルー
113
+ raise e
114
+ rescue StandardError, Exception => e
115
+ # にぎりつぶす
116
+ ensure
117
+ begin
118
+ execute_after(@after, context)
119
+ ensure
120
+ if result.failure.empty? && result.ba_empty?
121
+ result.result = Result::PASS
122
+ Progress.test_success(@name, test.name)
123
+ else
124
+ Progress.test_fail(@name, test.name)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end # class Case
130
+
131
+ class Manager
132
+ include Singleton
133
+ include Hook
134
+
135
+ attr_accessor :cases
136
+
137
+ def initialize
138
+ clear
139
+ end
140
+
141
+ def clear
142
+ remove_instance_var
143
+ flush
144
+ @cases = []
145
+ end
146
+
147
+ def flush
148
+ @beforeCase = []
149
+ @afterCase = []
150
+ @before = []
151
+ @after = []
152
+ @test = []
153
+ end
154
+
155
+ def beforeCase(option = {}, &block)
156
+ @beforeCase << Block.new("beforeCase", option, &block)
157
+ end
158
+
159
+ def afterCase(option = {}, &block)
160
+ @afterCase << Block.new("afterCase", option, &block)
161
+ end
162
+
163
+ # before test
164
+ def before(option = {}, &block)
165
+ @before << Block.new("before", option, &block)
166
+ end
167
+
168
+ # after test
169
+ def after(option = {}, &block)
170
+ @after << Block.new("after", option, &block)
171
+ end
172
+
173
+ # return product or args for value-parameterized test
174
+ def combine(*args)
175
+ if args.all? {|x| x.is_a? Array}
176
+ para = args.shift
177
+ args.each do |x|
178
+ para = para.product(x)
179
+ end
180
+ para.map {|x| x.flatten(1)}
181
+ else
182
+ raise ArgumentError, 'All arguments must be Array'
183
+ end
184
+ end
185
+
186
+ def test(name, option = {}, &block)
187
+ if option && option[:params]
188
+ # value-parameterized test
189
+ params = option[:params]
190
+ count = 0
191
+ params.each do |param|
192
+ test = Block.new("#{name}/#{count}", option, &block)
193
+ test.parameter = param
194
+ @test << test
195
+ count += 1
196
+ end
197
+ else
198
+ # normal test
199
+ @test << Block.new(name, option, &block)
200
+ end
201
+ end
202
+
203
+ def add(name)
204
+ (@beforeCase + @afterCase + @before + @after + @test).each do |block|
205
+ block.parent = name
206
+ end
207
+ @cases << Case.new(name, @beforeCase, @afterCase, @before, @after, @test)
208
+ flush
209
+ end
210
+ end # class Manager
211
+ end # module Test
212
+ end # module DTest