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