tet 1.4.1 → 1.5.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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tests.rb +32 -58
  3. data/lib/tet.rb +221 -158
  4. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1096a4a6ae70158e1f6f70394a2b9d5f246a18c
4
- data.tar.gz: 2b5bad1375b9dbdac13a038ae07360c1a81e27f2
3
+ metadata.gz: 97ce2949a018ea80d5012fc9dd00268098dd0ece
4
+ data.tar.gz: fefa2a09bfc0adf45730b15be19785812e75d535
5
5
  SHA512:
6
- metadata.gz: 9fd9ff0cc0747b0c8e4594e11855b550cb5d62661bdc648da8ef5f293f3594634bbae582f770fcff86c03041c8c26fe900d5996bcd5985fad49817db36b032c9
7
- data.tar.gz: 0102c03c6892f95f948d345497ff8b0fc785e317d0116ad48be4df82177290438e75809a021a2a6699990c36c05c4e78f9d0cda98e98c6ce8a334f32a78bbe7e
6
+ metadata.gz: c60b11a370e690471156621bce8884a3daaaa13bab34e0bb80cb0a9c8e52fb11b24d9f2231f42ff80d0aa65804e25000069095324bbfb3f0f2d125d5dfc0dc6c
7
+ data.tar.gz: '0719db9dedda2b62d2a413dd3b3342a28c87cff1855fb41b300e165aef30d2998156e67c3b153f1b7ef765f1cf1831e920b570efa613fb056e41d323aba95ab5'
data/lib/tests.rb CHANGED
@@ -1,77 +1,51 @@
1
- # Copyright (C) 2016 Colin Fulton
1
+ # Copyright (C) 2017 Colin Fulton
2
2
  # All rights reserved.
3
3
  #
4
4
  # This software may be modified and distributed under the
5
5
  # terms of the three-clause BSD license. See LICENSE.txt
6
6
  # (located in root directory of this project) for details.
7
7
 
8
- require_relative "./tet"
9
- require_relative "./test_helpers"
8
+ require_relative './tet'
10
9
 
11
- group "#assert" do
12
- assert("truthy blocks pass") { true }
10
+ class IntendedError < StandardError; end
11
+ class IntendedErrorChild < IntendedError; end
13
12
 
14
- result = assert { true }
15
- assert("passing returns true") { result.equal?(true) }
13
+ puts ".!?!!?!!!!!!.!.??????? (expected result)"
16
14
 
17
- result = should_fail { assert { false } }
18
- assert("failing returns false") { result.equal?(false) }
15
+ assert('true blocks pass') { true }
16
+ assert('false blocks fail') { false }
19
17
 
20
- should_fail do
21
- assert("falsy blocks fail") { nil }
22
- assert("empty assertions fail") { }
18
+ assert('errors are caught') { raise IntendedError }
23
19
 
24
- assert("can not nest #assert") { assert("nested") { true }; nil }
25
- assert("can not nest #group") { group("nested") { assert { true } }; nil }
26
- assert("can not nest #err") { err("nested") { not_a_method }; nil }
27
- end
20
+ group '#group' do
21
+ assert('first failure') { false }
22
+ assert('second failure') { false }
28
23
 
29
- should_err do
30
- assert("errors are caught and count as failures") { not_a_method }
31
- end
24
+ group('errors are caught') { raise IntendedError }
32
25
  end
33
26
 
34
- group "#group" do
35
- expected = "example output"
36
- result = group("EXAMPLE") do
37
- assert("EXAMPLE") { true }
38
- expected
39
- end
40
- assert("returns output of block") { result == expected }
41
-
42
- return_value = should_err { group { raise "Example Error" } }
43
- assert("returns nil when the block throws an error") { return_value.nil? }
44
-
45
- group 'fails when empty' do
46
- should_fail { group('group without content') { } }
47
- end
48
-
49
- group "can have classes for names" do
50
- group String do
51
- should_fail { assert { false } }
52
- end
53
- end
27
+ group 'anything can be a label' do
28
+ assert(:a_symbol) { false }
29
+ assert(Class) { false }
30
+ assert(Enumerable) { false }
31
+ assert(true) { false }
32
+ assert({x: 1, y: 2}) { false }
33
+ assert(nil) { false }
54
34
  end
55
35
 
56
- group "#err" do
57
- err("passes when there is an error") { not_a_method }
58
-
59
- err("specify an exception class", expect: NameError) { not_a_method }
60
- err("specify a parent exception class", expect: Exception) { not_a_method }
36
+ group '#err' do
37
+ err('errors pass') { raise IntendedError }
38
+ err('non-errors fail') { true }
61
39
 
62
- result = err { not_a_method }
63
- assert("passing returns true") { result.equal?(true) }
64
-
65
- result = should_fail { err { 1 + 1 } }
66
- assert("failing returns false") { result.equal?(false) }
67
-
68
- should_fail do
69
- err("no errors fails") { 1 + 1 }
70
- err("empty assertions fail") { }
71
- err("wrong class fails", expect: ArgumentError) { not_a_method }
40
+ err('correct errors pass', expect: IntendedError) { raise IntendedError }
41
+ err('incorrect errors err', expect: IntendedErrorChild) { raise IntendedError }
42
+ end
72
43
 
73
- err("can not nest #err") { err("nested") { not_a_method }; 1 + 1 }
74
- err("can not nest #assert") { assert("nested") { true }; 1 + 1 }
75
- err("can not nest #group") { group("nested") { assert { true } }; 1 + 1 }
76
- end
44
+ group 'should not nest' do
45
+ assert('#assert in #assert') { assert { true } }
46
+ assert('#group in #assert') { group('') { 'empty #group' } }
47
+ assert('#err in #assert') { err { raise IntendedError } }
48
+ err('#assert in #err') { assert { true } }
49
+ err('#group in #err') { group('') { 'empty #group' } }
50
+ err('#err in #err') { err { raise IntendedError } }
77
51
  end
data/lib/tet.rb CHANGED
@@ -1,222 +1,285 @@
1
- # Copyright (C) 2016 Colin Fulton
1
+ # Copyright (C) 2017 Colin Fulton
2
2
  # All rights reserved.
3
3
  #
4
4
  # This software may be modified and distributed under the
5
5
  # terms of the three-clause BSD license. See LICENSE.txt
6
6
  # (located in root directory of this project) for details.
7
7
 
8
- # Label all tests within a block.
9
- def group name = nil
10
- before = Tet.test_count
8
+ # Label a block of tests
9
+ def group label, &block
10
+ Tet.run(
11
+ label: label,
12
+ test: block
13
+ )
14
+ end
11
15
 
12
- Tet.in_group(name) do
13
- yield.tap do
14
- if Tet.test_count == before
15
- Tet.log_fail("EMPTY GROUP")
16
- end
16
+ # Assert that a block will return a truthy value
17
+ def assert label = '', &block
18
+ Tet.run(
19
+ label: label,
20
+ test: block,
21
+ no_nest: true,
22
+ truthy: -> { Tet.passed },
23
+ falsy: -> { Tet.failed }
24
+ )
25
+ end
26
+
27
+ # Assert that a block will err
28
+ def err label = '', expect: StandardError, &block
29
+ Tet.run(
30
+ label: label,
31
+ test: block,
32
+ no_nest: true,
33
+ truthy: -> { Tet.failed },
34
+ falsy: -> { Tet.failed },
35
+ error: ->(caught) {
36
+ (expect >= caught.class) ? Tet.passed : Tet.erred(caught, expect)
37
+ }
38
+ )
39
+ end
40
+
41
+
42
+
43
+ # A namespace for all of the helper methods and classes.
44
+ module Tet
45
+ # Print all the reports after all the tests have run
46
+ at_exit do
47
+ unless Stats.empty?
48
+ puts
49
+ puts_report 'Failures', Messages.failure_report
50
+ puts_report 'Exceptions', Messages.error_report
51
+ puts_report 'Statistics', Stats.report
17
52
  end
18
53
  end
19
- end
20
54
 
21
- # Declare that a block will return a truthy value.
22
- # If it doesn't or if it has an error, the test will be logged as failing.
23
- def assert name = nil
24
- Tet.in_group(name) do
25
- result = false
26
55
 
27
- Tet.stop_nesting("NESTED IN ASSERT: #{name}") do
28
- begin
29
- result = yield
30
56
 
31
- if result
32
- Tet.log_pass
33
- else
34
- Tet.log_fail
35
- end
36
- rescue StandardError => error_object
37
- Tet.log_error(error_object)
38
- end
57
+ # An exception class to distinguish errors from incorrectly written tests
58
+ class TestError < StandardError; end
59
+
60
+ # Object oriented way to do string formatting
61
+ module StringFormatting
62
+ refine String do
63
+ def indent
64
+ gsub(/^/, ' ')
39
65
  end
40
66
 
41
- !!result
67
+ def to_label
68
+ self
69
+ end
42
70
  end
43
- end
44
-
45
- # Declare that a block will have an error.
46
- # If it doesn't the test will be logged as failing.
47
- def err name = nil, expect: StandardError
48
- Tet.in_group(name) do
49
- result = false
50
71
 
51
- Tet.stop_nesting("NESTED IN ERR: #{name}") do
52
- begin
53
- yield
54
- Tet.log_fail
55
- rescue StandardError => error_object
56
- if expect >= error_object.class
57
- result = true
58
- Tet.log_pass
59
- else
60
- Tet.log_wrong_error(expected: expect, got: error_object)
61
- end
72
+ refine Module do
73
+ def to_label
74
+ name
62
75
  end
63
76
  end
64
77
 
65
- result
78
+ refine Object do
79
+ def to_label
80
+ inspect
81
+ end
82
+ end
66
83
  end
67
- end
68
84
 
69
- # A namespace for all of the helper methods.
70
- module Tet
71
- PassChar = "."
72
- FailChar = "F"
73
- ErrorChar = "!"
74
- Indent = " "
75
-
76
- @messages = []
77
- @current_group = []
78
- @test_count = 0
79
- @fail_count = 0
80
- @err_count = 0
81
- @nested_ban = false
85
+ using StringFormatting
86
+
82
87
 
88
+
89
+ # Helpers for building test methods
83
90
  class << self
84
- attr_reader :messages, :test_count, :fail_count, :err_count
91
+ # Call when an assertion has passed
92
+ def passed
93
+ Stats.passed
94
+ end
85
95
 
86
- # Store the group name for the duration of calling the given block.
87
- def in_group name
88
- result = nil
96
+ # Call when an assertion has failed
97
+ def failed
98
+ Stats.failed
99
+ Messages.failed
100
+ end
89
101
 
90
- @current_group << name.to_s if name
102
+ # Call when an assertion has erred
103
+ def erred caught, expected = nil
104
+ Stats.erred
105
+ Messages.erred(caught, expected)
106
+ end
91
107
 
92
- begin
93
- if @nested_ban
94
- log_fail @nested_ban
95
- else
96
- result = yield
108
+ # Run a block as a test
109
+ def run label:,
110
+ truthy: -> { },
111
+ falsy: -> { },
112
+ error: ->(caught) { erred(caught) },
113
+ no_nest: false,
114
+ test:
115
+
116
+ Messages.with_label(label) do
117
+ nesting_guard(no_nest) do
118
+ begin
119
+ test.call ? truthy.call : falsy.call
120
+ rescue TestError => caught
121
+ erred(caught)
122
+ rescue StandardError => caught
123
+ error.call(caught)
124
+ end
97
125
  end
98
- rescue StandardError => error_object
99
- log_error error_object, "ERROR IN GROUP"
100
126
  end
101
127
 
102
- @current_group.pop if name
128
+ nil
129
+ end
130
+
131
+ private
103
132
 
104
- result
133
+ # Print out a report to stdout
134
+ def puts_report header, content
135
+ unless content.empty?
136
+ puts
137
+ puts "#{header}:"
138
+ puts content.indent
139
+ end
105
140
  end
106
141
 
107
- def stop_nesting message
108
- @nested_ban = message
142
+ # Check and set a flag to prevent test blocks from nesting
143
+ def nesting_guard guard_state
144
+ raise TestError, 'assertions can not be nested' if @nesting_banned
145
+ @nesting_banned = guard_state
109
146
  yield
110
- @nested_ban = false
147
+ ensure
148
+ @nesting_banned = false
111
149
  end
150
+ end
112
151
 
113
- # Log a passing test.
114
- def log_pass
115
- print_now PassChar
116
152
 
117
- @test_count += 1
118
- end
119
153
 
120
- # Log a failing test.
121
- def log_fail *messages, letter: FailChar
122
- print_now letter
154
+ # Tracks and reports statistics about the tests that have run
155
+ module Stats
156
+ Counts = { passed: 0, failed: 0, erred: 0 }
157
+ Marks = { passed: ?., failed: ?!, erred: ?? }
123
158
 
124
- @test_count += 1
125
- @fail_count += 1
159
+ class << self
160
+ # Call when an assertion has passed
161
+ def passed
162
+ log :passed
163
+ end
126
164
 
165
+ # Call when an assertion has failed
166
+ def failed
167
+ log :failed
168
+ end
127
169
 
128
- group = @current_group.dup
129
- current_section = @messages
130
- messages = messages.map { |item| item.is_a?(Array) ? item : "✖ #{item}" }
170
+ # Call when an assertion has erred
171
+ def erred
172
+ log :erred
173
+ end
131
174
 
132
- # Walk down the tree of messages until either you find the current group's
133
- # array of messages OR the last section in common with the current group.
134
- until group.empty? || group.first != current_section[-2]
135
- group.shift
136
- current_section = current_section.last
175
+ # Returns true if no statistics have been logged
176
+ def empty?
177
+ Counts.values.inject(&:+).zero?
137
178
  end
138
179
 
139
- # If the messages were missing parts of this group fill out the remaining
140
- # group names.
141
- until group.empty?
142
- current_section << group.shift << []
143
- current_section = current_section.last
180
+ # Returns a string with statistics about the tests that have been run
181
+ def report
182
+ errors = Counts[:erred]
183
+ fails = Counts[:failed]
184
+ passes = Counts[:passed]
185
+
186
+ output = []
187
+ output << "Errors: #{errors}" unless errors.zero?
188
+ output << "Failed: #{fails}"
189
+ output << "Passed: #{passes}"
190
+
191
+ output.join(?\n)
144
192
  end
145
193
 
146
- # Append the new messages onto the current section.
147
- current_section.concat(messages)
148
- end
194
+ private
149
195
 
150
- # Log an error.
151
- def log_error error_object, *messages
152
- @err_count += 1
153
- log_fail *messages, *format_error(error_object), letter: ErrorChar
154
- end
196
+ # Log an event and print a mark to show progress is being made on tests
197
+ def log type
198
+ Counts[type] += 1
155
199
 
156
- # Log test which raised the wrong error.
157
- def log_wrong_error expected:, got:
158
- log_fail "EXPECTED: #{expected}", *format_error(got)
200
+ print Marks[type]
201
+ $stdout.flush
202
+ end
159
203
  end
204
+ end
205
+
206
+
207
+ # Tracks and reports messages for each test
208
+ module Messages
209
+ Labels = []
210
+ Results = []
211
+ Errors = []
212
+
213
+ class << self
214
+ # Add a label to all subsequent messages
215
+ def with_label label
216
+ Labels.push(label)
217
+ yield
218
+ ensure
219
+ Labels.pop
220
+ end
221
+
222
+ # Call when an assertion has failed
223
+ def failed
224
+ add_result!
225
+ end
160
226
 
161
- # Print stats and messages for all the failing tests.
162
- def render_result
163
- puts "\n" unless @test_count.zero?
227
+ # Call when an assertion has passed
228
+ def erred caught, expected = nil
229
+ number = Stats::Counts[:erred]
230
+ label = "EXCEPTION_#{number}"
231
+ label << " (expected: #{expected})" if expected
164
232
 
165
- print "#{plural @test_count, 'result'}, "
233
+ with_label(label) { add_result! }
166
234
 
167
- if (@fail_count + @err_count).zero?
168
- print "all good!"
169
- else
170
- print "#{plural @fail_count, 'fail'}"
171
- print " (including #{plural @err_count, 'error'})" unless @err_count.zero?
235
+ Errors.push(error_message(caught, number))
172
236
  end
173
237
 
174
- print "\n"
238
+ # Returns a string with details about all failed tests
239
+ def failure_report
240
+ Results.join(?\n)
241
+ end
175
242
 
176
- unless @messages.empty?
177
- puts "\nFailed tests:"
178
- puts indent(@messages)
243
+ # Returns a string with details about all errors
244
+ def error_report
245
+ Errors.join("\n\n")
179
246
  end
180
- end
181
247
 
182
- private
248
+ private
183
249
 
184
- # Format an error message so #indent will render it properly
185
- def format_error error_object
186
- [
187
- "ERROR: #{error_object.class}",
188
- [
189
- "#{error_object.message}",
190
- error_object.backtrace
191
- ]
192
- ]
193
- end
250
+ # Add a message for a new result using the current labels
251
+ def add_result!
252
+ Results.push(
253
+ Labels.map { |raw| raw.to_label }
254
+ .reject(&:empty?)
255
+ .join(' :: ')
256
+ )
257
+ end
194
258
 
195
- # Format an array of strings by joining them with \n and indenting nested
196
- # arrays deeper than their parents.
197
- def indent input, amount = 0
198
- case input
199
- when String
200
- input.gsub(/^/, Indent * amount)
201
- when Array
202
- input
203
- .reject(&:empty?)
204
- .map { |part| indent(part, amount + 1) }
205
- .join("\n")
259
+ def format_label label
260
+ case label
261
+ when Module
262
+ label.name
263
+ when String
264
+ label.to_s
265
+ else
266
+ label.inspect
267
+ end
206
268
  end
207
- end
208
269
 
209
- # Pluralize the given word.
210
- def plural amount, word
211
- "#{amount} #{word}#{amount != 1 ? "s" : ""}"
212
- end
270
+ # Format an error into a message string
271
+ def error_message error, number
272
+ error_message = error.to_s
273
+ error_class = error.class.to_s
274
+ error_message << " (#{error.class})" if error_message != error_class
275
+
276
+ summary = "#{number}. #{error_message}"
277
+ details = error.backtrace
278
+ .reject { |line| line.start_with?(__FILE__) }
279
+ .map { |line| line.indent }
213
280
 
214
- # Prevent delays in printing results.
215
- def print_now string
216
- print string
217
- $stdout.flush
281
+ details.unshift(summary).join(?\n)
282
+ end
218
283
  end
219
284
  end
220
-
221
- at_exit { Tet.render_result }
222
285
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Colin Fulton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-02-10 00:00:00.000000000 Z
11
+ date: 2017-03-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A very minimal test framework designed for simple projects. A couple
14
14
  of features, relatively nice looking output, and nothing else. Does the world need
@@ -40,7 +40,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
40
40
  version: '0'
41
41
  requirements: []
42
42
  rubyforge_project:
43
- rubygems_version: 2.5.1
43
+ rubygems_version: 2.6.8
44
44
  signing_key:
45
45
  specification_version: 4
46
46
  summary: Barely a test framework