smartest 0.2.0.alpha1 → 0.2.0.alpha2
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.
- checksums.yaml +4 -4
- data/DEVELOPMENT.md +56 -5
- data/README.md +29 -0
- data/SMARTEST_DESIGN.md +36 -9
- data/lib/smartest/errors.rb +24 -0
- data/lib/smartest/execution_context.rb +14 -0
- data/lib/smartest/fixture.rb +6 -0
- data/lib/smartest/hook_contexts.rb +10 -1
- data/lib/smartest/reporter.rb +22 -2
- data/lib/smartest/runner.rb +31 -13
- data/lib/smartest/test_result.rb +35 -2
- data/lib/smartest/test_run_state.rb +16 -0
- data/lib/smartest/version.rb +1 -1
- data/lib/smartest.rb +1 -0
- data/smartest/smartest_test.rb +161 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0953c11bf61f80c4a310701920e5b6a22727149ed135d86678b3e49b25c8a92
|
|
4
|
+
data.tar.gz: d51839a9cb597da3d63a15aa803661946ceb3ae2a6982317ea108797f4203380
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2e3be2ebdc5b116e6f16e396d4befda198c0063bb4da6e7f487266642d1edd8083bcc4dce9b37746969fdcbd9916424068f043344cc732edf4738f849fb87d9c
|
|
7
|
+
data.tar.gz: 0c299af8b8dd05fac80ec1eefaeade8f747f8d2776e87ba6cb684ff84c619be8a21dfd3e33c99fa43d05ee0d40aabc361f86ab1e33af696a5a9ef4c9ee78054d
|
data/DEVELOPMENT.md
CHANGED
|
@@ -224,6 +224,7 @@ Responsibilities:
|
|
|
224
224
|
- supports inheritance
|
|
225
225
|
- exposes `cleanup` to fixture blocks
|
|
226
226
|
- optionally delegates helper methods to `ExecutionContext`
|
|
227
|
+
- does not delegate `skip` or `pending` to fixture blocks
|
|
227
228
|
|
|
228
229
|
Fixture definitions should not execute at declaration time.
|
|
229
230
|
|
|
@@ -306,6 +307,7 @@ Responsibilities:
|
|
|
306
307
|
|
|
307
308
|
- include expectation methods
|
|
308
309
|
- include matchers
|
|
310
|
+
- expose `skip` and `pending` to test bodies
|
|
309
311
|
- expose helper methods
|
|
310
312
|
- optionally provide integration helpers later
|
|
311
313
|
|
|
@@ -329,6 +331,7 @@ Responsibilities:
|
|
|
329
331
|
- resolve test keyword fixtures
|
|
330
332
|
- run test body
|
|
331
333
|
- run cleanup in `ensure`
|
|
334
|
+
- track skipped and pending test state
|
|
332
335
|
- run suite fixture cleanup after all tests
|
|
333
336
|
- produce `TestResult`
|
|
334
337
|
- notify reporter
|
|
@@ -342,7 +345,8 @@ Pseudo-code:
|
|
|
342
345
|
|
|
343
346
|
```ruby
|
|
344
347
|
def run_one(test_case)
|
|
345
|
-
|
|
348
|
+
run_state = TestRunState.new
|
|
349
|
+
context = ExecutionContext.new(run_state: run_state)
|
|
346
350
|
fixture_set = FixtureSet.new(
|
|
347
351
|
@fixture_classes,
|
|
348
352
|
context: context,
|
|
@@ -353,9 +357,19 @@ def run_one(test_case)
|
|
|
353
357
|
|
|
354
358
|
context.instance_exec(**kwargs, &test_case.block)
|
|
355
359
|
|
|
356
|
-
|
|
360
|
+
if run_state.pending?
|
|
361
|
+
TestResult.failed(test_case, PendingPassedError.new(run_state.pending_reason))
|
|
362
|
+
else
|
|
363
|
+
TestResult.passed(test_case)
|
|
364
|
+
end
|
|
365
|
+
rescue Skipped => skipped
|
|
366
|
+
TestResult.skipped(test_case, skipped.reason)
|
|
357
367
|
rescue Exception => error
|
|
358
|
-
|
|
368
|
+
if run_state.pending?
|
|
369
|
+
TestResult.pending(test_case, run_state.pending_reason)
|
|
370
|
+
else
|
|
371
|
+
TestResult.failed(test_case, error)
|
|
372
|
+
end
|
|
359
373
|
ensure
|
|
360
374
|
fixture_set&.run_cleanups
|
|
361
375
|
end
|
|
@@ -370,13 +384,15 @@ Fields:
|
|
|
370
384
|
- test case
|
|
371
385
|
- status
|
|
372
386
|
- error
|
|
387
|
+
- reason
|
|
373
388
|
- duration
|
|
374
389
|
|
|
375
390
|
Statuses:
|
|
376
391
|
|
|
377
392
|
- passed
|
|
378
393
|
- failed
|
|
379
|
-
- skipped
|
|
394
|
+
- skipped
|
|
395
|
+
- pending
|
|
380
396
|
|
|
381
397
|
### `Smartest::Reporter`
|
|
382
398
|
|
|
@@ -385,7 +401,7 @@ Console reporter for MVP.
|
|
|
385
401
|
Responsibilities:
|
|
386
402
|
|
|
387
403
|
- print run start
|
|
388
|
-
- print per-test pass/fail
|
|
404
|
+
- print per-test pass/fail/skip/pending
|
|
389
405
|
- print failure details
|
|
390
406
|
- print summary
|
|
391
407
|
- return appropriate exit status through runner
|
|
@@ -565,6 +581,8 @@ module Smartest
|
|
|
565
581
|
class CircularFixtureDependencyError < Error; end
|
|
566
582
|
class InvalidFixtureParameterError < Error; end
|
|
567
583
|
class AssertionFailed < Error; end
|
|
584
|
+
class Skipped < Error; end
|
|
585
|
+
class PendingPassedError < AssertionFailed; end
|
|
568
586
|
end
|
|
569
587
|
```
|
|
570
588
|
|
|
@@ -649,6 +667,15 @@ A practical approach:
|
|
|
649
667
|
- expose `use_fixture` and `use_matcher` only inside hook contexts
|
|
650
668
|
- make `around_test` registered from `around_suite` suite-wide
|
|
651
669
|
|
|
670
|
+
### Phase 9: Skipped and pending tests
|
|
671
|
+
|
|
672
|
+
- `skip "reason"` inside test bodies and `around_test` hooks
|
|
673
|
+
- `pending "reason"` inside test bodies and `around_test` hooks
|
|
674
|
+
- skipped tests do not fail the run
|
|
675
|
+
- pending tests pass only when the remaining execution fails
|
|
676
|
+
- pending tests fail with `Smartest::PendingPassedError` when they pass
|
|
677
|
+
- `skip` and `pending` are not `test` metadata
|
|
678
|
+
|
|
652
679
|
## MVP API rules
|
|
653
680
|
|
|
654
681
|
Supported:
|
|
@@ -658,6 +685,19 @@ test("name") do
|
|
|
658
685
|
end
|
|
659
686
|
```
|
|
660
687
|
|
|
688
|
+
```ruby
|
|
689
|
+
test("name") do
|
|
690
|
+
skip "reason" if runtime_condition?
|
|
691
|
+
end
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
```ruby
|
|
695
|
+
test("name") do
|
|
696
|
+
pending "reason" if expected_to_fail?
|
|
697
|
+
expect(actual).to eq(expected)
|
|
698
|
+
end
|
|
699
|
+
```
|
|
700
|
+
|
|
661
701
|
```ruby
|
|
662
702
|
test("name") do |user:|
|
|
663
703
|
end
|
|
@@ -685,6 +725,7 @@ end
|
|
|
685
725
|
|
|
686
726
|
```ruby
|
|
687
727
|
around_test do |test|
|
|
728
|
+
pending "reason" if expected_to_fail?
|
|
688
729
|
test.run
|
|
689
730
|
end
|
|
690
731
|
```
|
|
@@ -706,6 +747,16 @@ fixture :client, with: [:server] do |server|
|
|
|
706
747
|
end
|
|
707
748
|
```
|
|
708
749
|
|
|
750
|
+
```ruby
|
|
751
|
+
test("name", skip: true) do
|
|
752
|
+
end
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
```ruby
|
|
756
|
+
test("name", pending: true) do
|
|
757
|
+
end
|
|
758
|
+
```
|
|
759
|
+
|
|
709
760
|
```ruby
|
|
710
761
|
resource :server do |use|
|
|
711
762
|
end
|
data/README.md
CHANGED
|
@@ -143,6 +143,34 @@ end
|
|
|
143
143
|
|
|
144
144
|
This makes fixture usage explicit and avoids relying on positional argument order.
|
|
145
145
|
|
|
146
|
+
## Skipping and pending tests
|
|
147
|
+
|
|
148
|
+
Use `skip` at the start of a test when the rest of the body should not run:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
test("PDF export") do |browser:|
|
|
152
|
+
skip "firefox is not supported" if browser.firefox?
|
|
153
|
+
|
|
154
|
+
export_pdf(browser)
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Use `pending` when the test should continue running but is expected to fail. If
|
|
159
|
+
the test passes after `pending`, Smartest fails it so the stale pending marker is
|
|
160
|
+
removed.
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
test("PDF export") do |browser:|
|
|
164
|
+
pending "Not supported by WebDriver BiDi yet" if browser.bidi?
|
|
165
|
+
|
|
166
|
+
export_pdf(browser)
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`skip` and `pending` are available in test bodies and `around_test` hooks, but
|
|
171
|
+
not as `test` metadata or fixture APIs. See
|
|
172
|
+
[Skipping Tests](documentation/docs/skipping-tests.md).
|
|
173
|
+
|
|
146
174
|
## Expectations
|
|
147
175
|
|
|
148
176
|
Smartest uses an expectation style:
|
|
@@ -648,6 +676,7 @@ The intended MVP includes:
|
|
|
648
676
|
- fixture cleanup
|
|
649
677
|
- suite hooks with `around_suite`
|
|
650
678
|
- test hooks with `around_test`
|
|
679
|
+
- skipped and pending tests through `skip` and `pending`
|
|
651
680
|
- `expect(...).to eq(...)`
|
|
652
681
|
- console reporter
|
|
653
682
|
- CLI runner
|
data/SMARTEST_DESIGN.md
CHANGED
|
@@ -299,7 +299,8 @@ This keeps the top-level DSL small.
|
|
|
299
299
|
|
|
300
300
|
Only `test`, `around_suite`, and `around_test` should be globally available when
|
|
301
301
|
using `smartest/autorun`. `use_fixture` and `use_matcher` are available only
|
|
302
|
-
inside hook execution contexts.
|
|
302
|
+
inside hook execution contexts. `skip` and `pending` are available only inside
|
|
303
|
+
test bodies and `around_test` hook execution contexts.
|
|
303
304
|
|
|
304
305
|
## Core architecture
|
|
305
306
|
|
|
@@ -691,6 +692,8 @@ end
|
|
|
691
692
|
Fixture blocks can call private methods because they execute with `instance_exec`.
|
|
692
693
|
|
|
693
694
|
Fixture classes may optionally delegate missing methods to the execution context.
|
|
695
|
+
They should not delegate `skip` or `pending`; those are test-body and
|
|
696
|
+
`around_test` controls, not fixture APIs.
|
|
694
697
|
|
|
695
698
|
This is useful for integration helpers.
|
|
696
699
|
|
|
@@ -831,28 +834,38 @@ Care must be taken not to run twice if both CLI and autorun are used.
|
|
|
831
834
|
|
|
832
835
|
## Exit status
|
|
833
836
|
|
|
834
|
-
- all tests passed: `0`
|
|
837
|
+
- all tests passed, skipped, or pending as expected: `0`
|
|
835
838
|
- any test failed: `1`
|
|
839
|
+
- pending test unexpectedly passed: `1`
|
|
836
840
|
- configuration/load error: `1`
|
|
837
841
|
- interrupted: re-raise or exit non-zero
|
|
838
842
|
|
|
839
843
|
## Metadata
|
|
840
844
|
|
|
841
|
-
`test`
|
|
845
|
+
`test` accepts metadata and stores it on `TestCase`, but metadata does not drive
|
|
846
|
+
runner behavior in the MVP:
|
|
842
847
|
|
|
843
848
|
```ruby
|
|
844
|
-
test("name", skip: true) do
|
|
845
|
-
end
|
|
846
|
-
|
|
847
849
|
test("name", tags: [:db]) do
|
|
848
850
|
end
|
|
849
851
|
```
|
|
850
852
|
|
|
851
|
-
|
|
853
|
+
Runtime skipping and pending behavior are method calls inside the test body or
|
|
854
|
+
`around_test`, not metadata:
|
|
855
|
+
|
|
856
|
+
```ruby
|
|
857
|
+
test("name") do
|
|
858
|
+
skip "reason" if runtime_condition?
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
test("name") do
|
|
862
|
+
pending "reason" if expected_to_fail?
|
|
863
|
+
expect(actual).to eq(expected)
|
|
864
|
+
end
|
|
865
|
+
```
|
|
852
866
|
|
|
853
867
|
Useful metadata later:
|
|
854
868
|
|
|
855
|
-
- `skip: true`
|
|
856
869
|
- `only: true`
|
|
857
870
|
- `tags: [:db]`
|
|
858
871
|
- `timeout: 5`
|
|
@@ -1198,11 +1211,25 @@ test("GET /health") do |client:|
|
|
|
1198
1211
|
end
|
|
1199
1212
|
```
|
|
1200
1213
|
|
|
1214
|
+
```ruby
|
|
1215
|
+
test("PDF export") do |browser:|
|
|
1216
|
+
skip "firefox is not supported" if browser.firefox?
|
|
1217
|
+
|
|
1218
|
+
export_pdf(browser)
|
|
1219
|
+
end
|
|
1220
|
+
|
|
1221
|
+
test("PDF export over BiDi") do |browser:|
|
|
1222
|
+
pending "Not supported by WebDriver BiDi yet" if browser.bidi?
|
|
1223
|
+
|
|
1224
|
+
export_pdf(browser)
|
|
1225
|
+
expect(browser.downloads).not_to be_empty
|
|
1226
|
+
end
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1201
1229
|
## Future ideas
|
|
1202
1230
|
|
|
1203
1231
|
Possible future features:
|
|
1204
1232
|
|
|
1205
|
-
- `skip`
|
|
1206
1233
|
- `only`
|
|
1207
1234
|
- tags
|
|
1208
1235
|
- custom reporters
|
data/lib/smartest/errors.rb
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Smartest
|
|
4
|
+
module StatusReason
|
|
5
|
+
DEFAULT_REASON = "No reason given"
|
|
6
|
+
|
|
7
|
+
def self.normalize(reason)
|
|
8
|
+
text = reason.to_s
|
|
9
|
+
text.empty? ? DEFAULT_REASON : text
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
4
13
|
class Error < StandardError; end
|
|
5
14
|
|
|
6
15
|
class FixtureNotFoundError < Error
|
|
@@ -65,4 +74,19 @@ module Smartest
|
|
|
65
74
|
class AroundTestRunError < Error; end
|
|
66
75
|
|
|
67
76
|
class AssertionFailed < Error; end
|
|
77
|
+
|
|
78
|
+
class Skipped < Error
|
|
79
|
+
attr_reader :reason
|
|
80
|
+
|
|
81
|
+
def initialize(reason = nil)
|
|
82
|
+
@reason = StatusReason.normalize(reason)
|
|
83
|
+
super(@reason)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class PendingPassedError < AssertionFailed
|
|
88
|
+
def initialize(reason = nil)
|
|
89
|
+
super("expected pending test to fail, but it passed: #{StatusReason.normalize(reason)}")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
68
92
|
end
|
|
@@ -4,5 +4,19 @@ module Smartest
|
|
|
4
4
|
class ExecutionContext
|
|
5
5
|
include Expectations
|
|
6
6
|
include Matchers
|
|
7
|
+
|
|
8
|
+
def initialize(run_state: TestRunState.new)
|
|
9
|
+
@run_state = run_state
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def skip(reason = nil)
|
|
15
|
+
raise Skipped, reason
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def pending(reason = nil)
|
|
19
|
+
@run_state.pending(reason)
|
|
20
|
+
end
|
|
7
21
|
end
|
|
8
22
|
end
|
data/lib/smartest/fixture.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Smartest
|
|
4
4
|
class Fixture
|
|
5
|
+
RESERVED_CONTEXT_METHODS = %i[skip pending].freeze
|
|
6
|
+
|
|
5
7
|
class << self
|
|
6
8
|
def fixture(name, scope: :test, &block)
|
|
7
9
|
define_fixture(
|
|
@@ -64,6 +66,8 @@ module Smartest
|
|
|
64
66
|
end
|
|
65
67
|
|
|
66
68
|
def method_missing(method_name, *args, &block)
|
|
69
|
+
return super if RESERVED_CONTEXT_METHODS.include?(method_name)
|
|
70
|
+
|
|
67
71
|
if @context.respond_to?(method_name, true)
|
|
68
72
|
@context.__send__(method_name, *args, &block)
|
|
69
73
|
else
|
|
@@ -72,6 +76,8 @@ module Smartest
|
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
def respond_to_missing?(method_name, include_private = false)
|
|
79
|
+
return super if RESERVED_CONTEXT_METHODS.include?(method_name)
|
|
80
|
+
|
|
75
81
|
@context.respond_to?(method_name, true) || super
|
|
76
82
|
end
|
|
77
83
|
end
|
|
@@ -30,8 +30,9 @@ module Smartest
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
class AroundTestContext
|
|
33
|
-
def initialize(test_run)
|
|
33
|
+
def initialize(test_run, run_state:)
|
|
34
34
|
@test_run = test_run
|
|
35
|
+
@run_state = run_state
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def call(hook, run_target = @test_run)
|
|
@@ -47,5 +48,13 @@ module Smartest
|
|
|
47
48
|
def use_matcher(matcher_module)
|
|
48
49
|
@test_run.add_matcher_module(matcher_module)
|
|
49
50
|
end
|
|
51
|
+
|
|
52
|
+
def skip(reason = nil)
|
|
53
|
+
raise Skipped, reason
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def pending(reason = nil)
|
|
57
|
+
@run_state.pending(reason)
|
|
58
|
+
end
|
|
50
59
|
end
|
|
51
60
|
end
|
data/lib/smartest/reporter.rb
CHANGED
|
@@ -4,6 +4,8 @@ module Smartest
|
|
|
4
4
|
class Reporter
|
|
5
5
|
PASS_MARK = "\u2713"
|
|
6
6
|
FAIL_MARK = "\u2717"
|
|
7
|
+
SKIP_MARK = "-"
|
|
8
|
+
PENDING_MARK = "*"
|
|
7
9
|
|
|
8
10
|
def initialize(io = $stdout)
|
|
9
11
|
@io = io
|
|
@@ -15,12 +17,13 @@ module Smartest
|
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def record(result)
|
|
18
|
-
|
|
19
|
-
@io.puts "#{mark} #{result.test_case.name}"
|
|
20
|
+
@io.puts record_line(result)
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def finish(results, suite_cleanup_errors: [], suite_errors: [])
|
|
23
24
|
failures = results.select(&:failed?)
|
|
25
|
+
skipped = results.select(&:skipped?)
|
|
26
|
+
pending = results.select(&:pending?)
|
|
24
27
|
|
|
25
28
|
report_failures(failures) if failures.any?
|
|
26
29
|
report_suite_errors(suite_errors) if suite_errors.any?
|
|
@@ -28,6 +31,8 @@ module Smartest
|
|
|
28
31
|
|
|
29
32
|
@io.puts
|
|
30
33
|
summary = "#{results.count} #{results.count == 1 ? 'test' : 'tests'}, #{results.count(&:passed?)} passed, #{failures.count} failed"
|
|
34
|
+
summary = "#{summary}, #{skipped.count} skipped" if skipped.any?
|
|
35
|
+
summary = "#{summary}, #{pending.count} pending" if pending.any?
|
|
31
36
|
if suite_errors.any?
|
|
32
37
|
suite_label = suite_errors.count == 1 ? "suite failure" : "suite failures"
|
|
33
38
|
summary = "#{summary}, #{suite_errors.count} #{suite_label}"
|
|
@@ -41,6 +46,21 @@ module Smartest
|
|
|
41
46
|
|
|
42
47
|
private
|
|
43
48
|
|
|
49
|
+
def record_line(result)
|
|
50
|
+
case result.status
|
|
51
|
+
when :passed
|
|
52
|
+
"#{PASS_MARK} #{result.test_case.name}"
|
|
53
|
+
when :failed
|
|
54
|
+
"#{FAIL_MARK} #{result.test_case.name}"
|
|
55
|
+
when :skipped
|
|
56
|
+
"#{SKIP_MARK} #{result.test_case.name} (skipped: #{result.reason})"
|
|
57
|
+
when :pending
|
|
58
|
+
"#{PENDING_MARK} #{result.test_case.name} (pending: #{result.reason})"
|
|
59
|
+
else
|
|
60
|
+
"#{FAIL_MARK} #{result.test_case.name}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
44
64
|
def report_failures(failures)
|
|
45
65
|
@io.puts
|
|
46
66
|
@io.puts "Failures:"
|
data/lib/smartest/runner.rb
CHANGED
|
@@ -67,16 +67,20 @@ module Smartest
|
|
|
67
67
|
def run_one(test_case)
|
|
68
68
|
started_at = now
|
|
69
69
|
error = nil
|
|
70
|
+
skipped = nil
|
|
70
71
|
cleanup_errors = []
|
|
72
|
+
run_state = TestRunState.new
|
|
71
73
|
test_run = TestRun.new(
|
|
72
74
|
fixture_classes: @suite.fixture_classes,
|
|
73
75
|
matcher_modules: @suite.matcher_modules
|
|
74
76
|
) do |fixture_classes:, matcher_modules:|
|
|
75
|
-
|
|
77
|
+
run_test_body(test_case, fixture_classes, matcher_modules, run_state, cleanup_errors)
|
|
76
78
|
end
|
|
77
79
|
|
|
78
80
|
begin
|
|
79
|
-
run_around_test_hooks(@suite.around_test_hooks + test_case.around_test_hooks, test_run)
|
|
81
|
+
run_around_test_hooks(@suite.around_test_hooks + test_case.around_test_hooks, test_run, run_state)
|
|
82
|
+
rescue Skipped => skipped_error
|
|
83
|
+
skipped = skipped_error
|
|
80
84
|
rescue Exception => rescued_error
|
|
81
85
|
raise if Smartest.fatal_exception?(rescued_error)
|
|
82
86
|
|
|
@@ -85,6 +89,19 @@ module Smartest
|
|
|
85
89
|
|
|
86
90
|
duration = now - started_at
|
|
87
91
|
|
|
92
|
+
return TestResult.failed(test_case: test_case, error: nil, duration: duration, cleanup_errors: cleanup_errors) if skipped && cleanup_errors.any?
|
|
93
|
+
return TestResult.skipped(test_case: test_case, reason: skipped.reason, duration: duration) if skipped
|
|
94
|
+
|
|
95
|
+
if run_state.pending?
|
|
96
|
+
if error && !around_test_protocol_error?(error)
|
|
97
|
+
return TestResult.failed(test_case: test_case, error: nil, duration: duration, cleanup_errors: cleanup_errors) if cleanup_errors.any?
|
|
98
|
+
|
|
99
|
+
return TestResult.pending(test_case: test_case, reason: run_state.pending_reason, duration: duration)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
error ||= PendingPassedError.new(run_state.pending_reason)
|
|
103
|
+
end
|
|
104
|
+
|
|
88
105
|
if error || cleanup_errors.any?
|
|
89
106
|
TestResult.failed(
|
|
90
107
|
test_case: test_case,
|
|
@@ -97,23 +114,20 @@ module Smartest
|
|
|
97
114
|
end
|
|
98
115
|
end
|
|
99
116
|
|
|
100
|
-
def run_test_body(test_case, fixture_classes, matcher_modules)
|
|
101
|
-
context = build_context(matcher_modules)
|
|
117
|
+
def run_test_body(test_case, fixture_classes, matcher_modules, run_state, cleanup_errors)
|
|
118
|
+
context = build_context(matcher_modules, run_state)
|
|
102
119
|
fixture_set = nil
|
|
103
|
-
cleanup_errors = []
|
|
104
120
|
|
|
105
121
|
begin
|
|
106
122
|
fixture_set = FixtureSet.new(fixture_classes, context: context, parent: suite_fixture_set)
|
|
107
123
|
fixtures = fixture_set.resolve_keywords(test_case.fixture_names)
|
|
108
124
|
context.instance_exec(**fixtures, &test_case.block)
|
|
109
125
|
ensure
|
|
110
|
-
cleanup_errors
|
|
126
|
+
cleanup_errors.concat(fixture_set.run_cleanups) if fixture_set
|
|
111
127
|
end
|
|
112
|
-
|
|
113
|
-
cleanup_errors
|
|
114
128
|
end
|
|
115
129
|
|
|
116
|
-
def run_around_test_hooks(hooks, test_run, index = 0)
|
|
130
|
+
def run_around_test_hooks(hooks, test_run, run_state, index = 0)
|
|
117
131
|
return test_run.run if index >= hooks.length
|
|
118
132
|
|
|
119
133
|
hook = hooks[index]
|
|
@@ -121,10 +135,10 @@ module Smartest
|
|
|
121
135
|
fixture_classes: [],
|
|
122
136
|
matcher_modules: []
|
|
123
137
|
) do |**_keywords|
|
|
124
|
-
run_around_test_hooks(hooks, test_run, index + 1)
|
|
138
|
+
run_around_test_hooks(hooks, test_run, run_state, index + 1)
|
|
125
139
|
end
|
|
126
140
|
|
|
127
|
-
AroundTestContext.new(test_run).call(hook, next_run)
|
|
141
|
+
AroundTestContext.new(test_run, run_state: run_state).call(hook, next_run)
|
|
128
142
|
raise AroundTestRunError, "around_test hook did not call test.run" unless next_run.ran?
|
|
129
143
|
|
|
130
144
|
next_run.result
|
|
@@ -138,12 +152,16 @@ module Smartest
|
|
|
138
152
|
)
|
|
139
153
|
end
|
|
140
154
|
|
|
141
|
-
def build_context(matcher_modules = @suite.matcher_modules)
|
|
142
|
-
ExecutionContext.new.tap do |context|
|
|
155
|
+
def build_context(matcher_modules = @suite.matcher_modules, run_state = TestRunState.new)
|
|
156
|
+
ExecutionContext.new(run_state: run_state).tap do |context|
|
|
143
157
|
matcher_modules.each { |matcher_module| context.extend(matcher_module) }
|
|
144
158
|
end
|
|
145
159
|
end
|
|
146
160
|
|
|
161
|
+
def around_test_protocol_error?(error)
|
|
162
|
+
error.is_a?(AroundTestRunError)
|
|
163
|
+
end
|
|
164
|
+
|
|
147
165
|
def now
|
|
148
166
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
149
167
|
end
|
data/lib/smartest/test_result.rb
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Smartest
|
|
4
4
|
class TestResult
|
|
5
|
-
attr_reader :test_case, :status, :error, :duration, :cleanup_errors
|
|
5
|
+
attr_reader :test_case, :status, :error, :duration, :cleanup_errors, :reason
|
|
6
6
|
|
|
7
7
|
def self.passed(test_case:, duration:, cleanup_errors: [])
|
|
8
8
|
new(
|
|
9
9
|
test_case: test_case,
|
|
10
10
|
status: :passed,
|
|
11
11
|
error: nil,
|
|
12
|
+
reason: nil,
|
|
12
13
|
duration: duration,
|
|
13
14
|
cleanup_errors: cleanup_errors
|
|
14
15
|
)
|
|
@@ -19,15 +20,39 @@ module Smartest
|
|
|
19
20
|
test_case: test_case,
|
|
20
21
|
status: :failed,
|
|
21
22
|
error: error,
|
|
23
|
+
reason: nil,
|
|
22
24
|
duration: duration,
|
|
23
25
|
cleanup_errors: cleanup_errors
|
|
24
26
|
)
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
def
|
|
29
|
+
def self.skipped(test_case:, reason:, duration:, cleanup_errors: [])
|
|
30
|
+
new(
|
|
31
|
+
test_case: test_case,
|
|
32
|
+
status: :skipped,
|
|
33
|
+
error: nil,
|
|
34
|
+
reason: reason,
|
|
35
|
+
duration: duration,
|
|
36
|
+
cleanup_errors: cleanup_errors
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.pending(test_case:, reason:, duration:, cleanup_errors: [])
|
|
41
|
+
new(
|
|
42
|
+
test_case: test_case,
|
|
43
|
+
status: :pending,
|
|
44
|
+
error: nil,
|
|
45
|
+
reason: reason,
|
|
46
|
+
duration: duration,
|
|
47
|
+
cleanup_errors: cleanup_errors
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def initialize(test_case:, status:, error:, reason:, duration:, cleanup_errors:)
|
|
28
52
|
@test_case = test_case
|
|
29
53
|
@status = status
|
|
30
54
|
@error = error
|
|
55
|
+
@reason = reason
|
|
31
56
|
@duration = duration
|
|
32
57
|
@cleanup_errors = cleanup_errors
|
|
33
58
|
end
|
|
@@ -39,5 +64,13 @@ module Smartest
|
|
|
39
64
|
def failed?
|
|
40
65
|
status == :failed
|
|
41
66
|
end
|
|
67
|
+
|
|
68
|
+
def skipped?
|
|
69
|
+
status == :skipped
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def pending?
|
|
73
|
+
status == :pending
|
|
74
|
+
end
|
|
42
75
|
end
|
|
43
76
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smartest
|
|
4
|
+
class TestRunState
|
|
5
|
+
attr_reader :pending_reason
|
|
6
|
+
|
|
7
|
+
def pending(reason = nil)
|
|
8
|
+
@pending_reason = StatusReason.normalize(reason)
|
|
9
|
+
nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def pending?
|
|
13
|
+
!@pending_reason.nil?
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/smartest/version.rb
CHANGED
data/lib/smartest.rb
CHANGED
|
@@ -12,6 +12,7 @@ require_relative "smartest/matcher_registry"
|
|
|
12
12
|
require_relative "smartest/fixture_set"
|
|
13
13
|
require_relative "smartest/suite"
|
|
14
14
|
require_relative "smartest/suite_run"
|
|
15
|
+
require_relative "smartest/test_run_state"
|
|
15
16
|
require_relative "smartest/test_run"
|
|
16
17
|
require_relative "smartest/hook_contexts"
|
|
17
18
|
require_relative "smartest/expectations"
|
data/smartest/smartest_test.rb
CHANGED
|
@@ -79,6 +79,92 @@ test("reports expectation failures") do
|
|
|
79
79
|
expect(output).to include("1 test, 0 passed, 1 failed")
|
|
80
80
|
end
|
|
81
81
|
|
|
82
|
+
test("skip marks a test as skipped and stops the body") do
|
|
83
|
+
events = []
|
|
84
|
+
suite = Smartest::Suite.new
|
|
85
|
+
suite.tests.add(
|
|
86
|
+
SmartestSelfTest.test_case(
|
|
87
|
+
"unsupported export",
|
|
88
|
+
proc do
|
|
89
|
+
events << :before_skip
|
|
90
|
+
skip "firefox is not supported"
|
|
91
|
+
events << :after_skip
|
|
92
|
+
end
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
97
|
+
|
|
98
|
+
expect(status).to eq(0)
|
|
99
|
+
expect(events).to eq([:before_skip])
|
|
100
|
+
expect(output).to include("- unsupported export (skipped: firefox is not supported)")
|
|
101
|
+
expect(output).to include("1 test, 0 passed, 0 failed, 1 skipped")
|
|
102
|
+
expect(output).not_to include("Failures:")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
test("pending marks a failing test as pending") do
|
|
106
|
+
events = []
|
|
107
|
+
suite = Smartest::Suite.new
|
|
108
|
+
suite.tests.add(
|
|
109
|
+
SmartestSelfTest.test_case(
|
|
110
|
+
"bidi export",
|
|
111
|
+
proc do
|
|
112
|
+
pending "Not supported by WebDriver BiDi yet"
|
|
113
|
+
events << :after_pending
|
|
114
|
+
expect("actual").to eq("expected")
|
|
115
|
+
events << :after_failure
|
|
116
|
+
end
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
121
|
+
|
|
122
|
+
expect(status).to eq(0)
|
|
123
|
+
expect(events).to eq([:after_pending])
|
|
124
|
+
expect(output).to include("* bidi export (pending: Not supported by WebDriver BiDi yet)")
|
|
125
|
+
expect(output).to include("1 test, 0 passed, 0 failed, 1 pending")
|
|
126
|
+
expect(output).not_to include("Failures:")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
test("pending fails when the test passes") do
|
|
130
|
+
suite = Smartest::Suite.new
|
|
131
|
+
suite.tests.add(
|
|
132
|
+
SmartestSelfTest.test_case(
|
|
133
|
+
"fixed bidi export",
|
|
134
|
+
proc do
|
|
135
|
+
pending "Not supported by WebDriver BiDi yet"
|
|
136
|
+
expect("actual").to eq("actual")
|
|
137
|
+
end
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
142
|
+
|
|
143
|
+
expect(status).to eq(1)
|
|
144
|
+
expect(output).to include("expected pending test to fail, but it passed: Not supported by WebDriver BiDi yet")
|
|
145
|
+
expect(output).to include("1 test, 0 passed, 1 failed")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
test("skip and pending are not available inside fixtures") do
|
|
149
|
+
%i[skip pending].each do |method_name|
|
|
150
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
151
|
+
fixture :value do
|
|
152
|
+
__send__(method_name, "reason")
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
suite = Smartest::Suite.new
|
|
157
|
+
suite.fixture_classes.add(fixture_class)
|
|
158
|
+
suite.tests.add(SmartestSelfTest.test_case("#{method_name} fixture", proc { |value:| expect(value).to eq(:value) }))
|
|
159
|
+
|
|
160
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
161
|
+
|
|
162
|
+
expect(status).to eq(1)
|
|
163
|
+
expect(output).to include("NoMethodError")
|
|
164
|
+
expect(output).to include(method_name.to_s)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
82
168
|
test("supports basic matchers") do
|
|
83
169
|
suite = Smartest::Suite.new
|
|
84
170
|
suite.tests.add(
|
|
@@ -563,6 +649,81 @@ test("around_test must call test.run") do
|
|
|
563
649
|
expect(output).to include("Smartest::AroundTestRunError: around_test hook did not call test.run")
|
|
564
650
|
end
|
|
565
651
|
|
|
652
|
+
test("around_test can skip before test.run") do
|
|
653
|
+
events = []
|
|
654
|
+
suite = Smartest::Suite.new
|
|
655
|
+
suite.tests.add(
|
|
656
|
+
Smartest::TestCase.new(
|
|
657
|
+
name: "skipped by hook",
|
|
658
|
+
metadata: {},
|
|
659
|
+
location: caller_locations(1, 1).first,
|
|
660
|
+
around_test_hooks: [
|
|
661
|
+
proc do |test_run|
|
|
662
|
+
events << :around_before
|
|
663
|
+
skip "browser is not supported"
|
|
664
|
+
test_run.run
|
|
665
|
+
events << :around_after
|
|
666
|
+
end
|
|
667
|
+
],
|
|
668
|
+
block: proc { events << :test_body }
|
|
669
|
+
)
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
673
|
+
|
|
674
|
+
expect(status).to eq(0)
|
|
675
|
+
expect(events).to eq([:around_before])
|
|
676
|
+
expect(output).to include("- skipped by hook (skipped: browser is not supported)")
|
|
677
|
+
expect(output).to include("1 test, 0 passed, 0 failed, 1 skipped")
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
test("around_test can mark a failing test as pending") do
|
|
681
|
+
suite = Smartest::Suite.new
|
|
682
|
+
suite.tests.add(
|
|
683
|
+
Smartest::TestCase.new(
|
|
684
|
+
name: "pending by hook",
|
|
685
|
+
metadata: {},
|
|
686
|
+
location: caller_locations(1, 1).first,
|
|
687
|
+
around_test_hooks: [
|
|
688
|
+
proc do |test_run|
|
|
689
|
+
pending "driver bug"
|
|
690
|
+
test_run.run
|
|
691
|
+
end
|
|
692
|
+
],
|
|
693
|
+
block: proc { expect("actual").to eq("expected") }
|
|
694
|
+
)
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
698
|
+
|
|
699
|
+
expect(status).to eq(0)
|
|
700
|
+
expect(output).to include("* pending by hook (pending: driver bug)")
|
|
701
|
+
expect(output).to include("1 test, 0 passed, 0 failed, 1 pending")
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
test("pending around_test hooks must still call test.run") do
|
|
705
|
+
suite = Smartest::Suite.new
|
|
706
|
+
suite.tests.add(
|
|
707
|
+
Smartest::TestCase.new(
|
|
708
|
+
name: "pending hook missing run",
|
|
709
|
+
metadata: {},
|
|
710
|
+
location: caller_locations(1, 1).first,
|
|
711
|
+
around_test_hooks: [
|
|
712
|
+
proc do |_test_run|
|
|
713
|
+
pending "driver bug"
|
|
714
|
+
end
|
|
715
|
+
],
|
|
716
|
+
block: proc { expect(true).to eq(false) }
|
|
717
|
+
)
|
|
718
|
+
)
|
|
719
|
+
|
|
720
|
+
status, output = SmartestSelfTest.run_suite(suite)
|
|
721
|
+
|
|
722
|
+
expect(status).to eq(1)
|
|
723
|
+
expect(output).to include("Smartest::AroundTestRunError: around_test hook did not call test.run")
|
|
724
|
+
expect(output).to include("1 test, 0 passed, 1 failed")
|
|
725
|
+
end
|
|
726
|
+
|
|
566
727
|
test("use_fixture and use_matcher are only available inside hooks") do
|
|
567
728
|
{
|
|
568
729
|
"use_fixture Object" => "use_fixture",
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smartest
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.0.
|
|
4
|
+
version: 0.2.0.alpha2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yusuke Iwaki
|
|
@@ -65,6 +65,7 @@ files:
|
|
|
65
65
|
- lib/smartest/test_registry.rb
|
|
66
66
|
- lib/smartest/test_result.rb
|
|
67
67
|
- lib/smartest/test_run.rb
|
|
68
|
+
- lib/smartest/test_run_state.rb
|
|
68
69
|
- lib/smartest/version.rb
|
|
69
70
|
- smartest.gemspec
|
|
70
71
|
- smartest/smartest_test.rb
|