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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee5c1b7be401fa3dc4a8d88a25a241fc2b0bad486e8a3163a83c6c2340ae7c3c
4
- data.tar.gz: f063204cd31bcd99b4654c26bb8591d566dcd424a2a312d9a7c9cbbea4292b01
3
+ metadata.gz: e0953c11bf61f80c4a310701920e5b6a22727149ed135d86678b3e49b25c8a92
4
+ data.tar.gz: d51839a9cb597da3d63a15aa803661946ceb3ae2a6982317ea108797f4203380
5
5
  SHA512:
6
- metadata.gz: 18f117d867c86f47ff3d517729c0e7cbae4af5933b7432fa96ad30b1aea58a76b48e7ae4d3c129e72594598d19297751513d49501e5481c9ef07259a5350dafb
7
- data.tar.gz: '085a5a10a7d5248d7888fd6267edae7c4dd88077f5b413d45385d4c05245b0eae771975ab67a50befc8ca14d8acb3d148d9718d94b431caf9800d2a2daa78627'
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
- context = ExecutionContext.new
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
- TestResult.passed(test_case)
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
- TestResult.failed(test_case, error)
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, later
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` should accept metadata:
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
- MVP can store metadata without implementing all behavior.
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
@@ -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
@@ -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
@@ -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
- mark = result.passed? ? PASS_MARK : FAIL_MARK
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:"
@@ -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
- cleanup_errors = run_test_body(test_case, fixture_classes, matcher_modules)
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 = fixture_set.run_cleanups if fixture_set
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
@@ -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 initialize(test_case:, status:, error:, duration:, cleanup_errors:)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Smartest
4
- VERSION = "0.2.0.alpha1"
4
+ VERSION = "0.2.0.alpha2"
5
5
  end
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"
@@ -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.alpha1
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