yardcheck 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.buildkite/hooks/pre-command +3 -0
  3. data/.buildkite/pipeline.yml +10 -0
  4. data/.gitmodules +3 -0
  5. data/.rubocop.yml +15 -13
  6. data/Gemfile +3 -2
  7. data/README.md +19 -4
  8. data/lib/yardcheck/const.rb +27 -1
  9. data/lib/yardcheck/documentation/method_object.rb +21 -3
  10. data/lib/yardcheck/method_call.rb +37 -10
  11. data/lib/yardcheck/method_tracer.rb +50 -11
  12. data/lib/yardcheck/observation.rb +32 -6
  13. data/lib/yardcheck/processed_source.rb +32 -0
  14. data/lib/yardcheck/runner.rb +6 -0
  15. data/lib/yardcheck/typedef/parser.rb +1 -1
  16. data/lib/yardcheck/typedef.rb +16 -2
  17. data/lib/yardcheck/version.rb +1 -1
  18. data/lib/yardcheck/violation.rb +38 -0
  19. data/lib/yardcheck.rb +2 -0
  20. data/spec/integration/yardcheck_spec.rb +9 -3
  21. data/spec/spec_helper.rb +5 -1
  22. data/spec/unit/yardcheck/const_spec.rb +14 -0
  23. data/spec/unit/yardcheck/documentation_spec.rb +17 -2
  24. data/spec/unit/yardcheck/method_tracer_spec.rb +4 -6
  25. data/spec/unit/yardcheck/processed_source_spec.rb +43 -0
  26. data/spec/unit/yardcheck/runner_spec.rb +19 -20
  27. data/test_app/Gemfile +1 -0
  28. data/test_app/Gemfile.lock +9 -4
  29. data/test_app/lib/test_app/ambiguous_raise.rb +33 -0
  30. data/test_app/lib/test_app/block_return.rb +40 -0
  31. data/test_app/lib/test_app/tracepoint_bug.rb +23 -0
  32. data/test_app/lib/test_app.rb +32 -1
  33. data/test_app/spec/test_app/ambiguous_raise_spec.rb +9 -0
  34. data/test_app/spec/test_app/block_return_spec.rb +9 -0
  35. data/test_app/spec/test_app/tracepoint_bug_spec.rb +7 -0
  36. data/test_app/spec/test_app_spec.rb +31 -13
  37. data/yardcheck.gemspec +1 -0
  38. metadata +27 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 88750f901e803de9b757d1699df2284e811e1b46
4
- data.tar.gz: 47016f5393d602d75bbaf3a736735b9d93680eb9
3
+ metadata.gz: b594df9271ab2ef4f110b5fdaf97a44fc3e633a8
4
+ data.tar.gz: be08bcfad68850ed0adca3b1dba4e38562de2840
5
5
  SHA512:
6
- metadata.gz: 688559a7d4caebc0218a1193ed67add77977e4c191e1f58767fa85e0120503a333d4146fe148d9f205c6940972f4d2451883e91fc21d244f3cc7575a3ac49b85
7
- data.tar.gz: 3ae9fe9623004ec459faf4a1cfb475c7f60e7e4f2f47a177ae160360d92a5ef6c44a24fe3acd00c067fb09e0d9eb072be1dcae284dc7949fcb117731b760cdc6
6
+ metadata.gz: 5406813ffe5bda64d642a76cf9e6aab7c54fa0953e7bd693d5b98044048702cee8860c1af011fb48eb671a353d95b06e229b2996259180ac07e8ffb1cda68377
7
+ data.tar.gz: afc03fd46651422fbef24949eb940b175b4c6d62fb532c790b893482be060449d032c2202d02663c5f47f9dc2682069205da7cc5e403badaf66257ed78645bba
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ docker pull backus/ruby-matrix
@@ -0,0 +1,10 @@
1
+ steps:
2
+ - command: "build/run 2.3.3 'rspec'"
3
+ label: ":rspec: RSpec"
4
+ agents:
5
+ - queue=elastic
6
+
7
+ - name: ':rubocop: Rubocop'
8
+ command: build/run 2.3.3 rubocop
9
+ agents:
10
+ - queue=elastic
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "build"]
2
+ path = build
3
+ url = https://github.com/backus/buildkite-ruby-matrix.git
data/.rubocop.yml CHANGED
@@ -13,7 +13,7 @@ AllCops:
13
13
  # This ends up being too spammy
14
14
  Style/Documentation:
15
15
  Enabled: false
16
- Style/ExtraSpacing:
16
+ Layout/ExtraSpacing:
17
17
  AllowForAlignment: true
18
18
  Metrics/LineLength:
19
19
  Max: 100
@@ -50,38 +50,38 @@ Style/Send:
50
50
  Enabled: true
51
51
  Style/AutoResourceCleanup:
52
52
  Enabled: true
53
- Style/FirstArrayElementLineBreak:
53
+ Layout/FirstArrayElementLineBreak:
54
54
  Enabled: true
55
- Style/FirstHashElementLineBreak:
55
+ Layout/FirstHashElementLineBreak:
56
56
  Enabled: true
57
- Style/FirstMethodArgumentLineBreak:
57
+ Layout/FirstMethodArgumentLineBreak:
58
58
  Enabled: true
59
- Style/FirstMethodParameterLineBreak:
59
+ Layout/FirstMethodParameterLineBreak:
60
60
  Enabled: true
61
- Style/MultilineArrayBraceLayout:
61
+ Layout/MultilineArrayBraceLayout:
62
62
  Enabled: true
63
- Style/MultilineAssignmentLayout:
63
+ Layout/MultilineAssignmentLayout:
64
64
  EnforcedStyle: new_line
65
65
  Enabled: true
66
- Style/MultilineHashBraceLayout:
66
+ Layout/MultilineHashBraceLayout:
67
67
  Enabled: true
68
- Style/MultilineMethodCallBraceLayout:
68
+ Layout/MultilineMethodCallBraceLayout:
69
69
  Enabled: true
70
- Style/MultilineMethodDefinitionBraceLayout:
70
+ Layout/MultilineMethodDefinitionBraceLayout:
71
71
  Enabled: true
72
72
  Style/OptionHash:
73
73
  Enabled: true
74
74
  Style/StringMethods:
75
75
  Enabled: true
76
- Style/IndentArray:
76
+ Layout/IndentArray:
77
77
  EnforcedStyle: consistent
78
- Style/IndentHash:
78
+ Layout/IndentHash:
79
79
  EnforcedStyle: consistent
80
80
  MultilineMethodCallIndentation:
81
81
  EnforcedStyle: indented
82
82
  Style/Alias:
83
83
  EnforcedStyle: prefer_alias_method
84
- Style/AlignHash:
84
+ Layout/AlignHash:
85
85
  EnforcedColonStyle: table
86
86
  Style/SignalException:
87
87
  EnforcedStyle: semantic
@@ -212,3 +212,5 @@ Lint/DefEndAlignment:
212
212
  # See explanation for `Lint/DefEndAlignment`
213
213
  Lint/EndAlignment:
214
214
  AutoCorrect: true
215
+ Style/MixinGrouping:
216
+ EnforcedStyle: grouped
data/Gemfile CHANGED
@@ -11,9 +11,10 @@ group :test do
11
11
  end
12
12
 
13
13
  group :lint do
14
- gem 'rubocop', git: 'https://github.com/bbatsov/rubocop.git'
14
+ gem 'rake' # sickill/rainbow#44
15
+ gem 'rubocop', '~> 0.49.1'
15
16
  gem 'rubocop-devtools', git: 'https://github.com/backus/rubocop-devtools.git'
16
- gem 'rubocop-rspec', git: 'https://github.com/backus/rubocop-rspec.git'
17
+ gem 'rubocop-rspec'
17
18
  end
18
19
 
19
20
  gem 'guard'
data/README.md CHANGED
@@ -14,9 +14,9 @@ When you write documentation like this
14
14
  # @param user [User]
15
15
  #
16
16
  # @return [true,false]
17
- # def valid?(user)
18
- # ...
19
- # end
17
+ def valid?(user)
18
+ # ...
19
+ end
20
20
  ```
21
21
 
22
22
  You are saying that you are always going to be passing in a `User` instance and the method will always returns `true` or `false`.
@@ -74,10 +74,25 @@ Yardcheck is doing some cool things here:
74
74
 
75
75
  In this case I would update the documentation to be `@param [#call, nil] block`
76
76
 
77
- # Is this ready?
77
+ ## Is this ready?
78
78
 
79
79
  Kind of.
80
80
 
81
81
  It is not ready to be run in CI to check your documentation and it may never be since tracing method calls is fairly slow. We also sometimes mess up. For example, if another method raises an error then all of the methods that bubble up that error without rescuing it will be marked as returning `nil`. This seems like a limitation of ruby's `TracePoint` right now.
82
82
 
83
83
  It is very helpful though. It will find a lot of cases where your documentation isn't quite right and the output is clear. Install it and give it a try.
84
+
85
+ ## Install
86
+
87
+ You probably could have guessed this, but to install just run
88
+
89
+ ```
90
+ $ gem install yardcheck
91
+ ```
92
+
93
+ Or add this to your Gemfile
94
+
95
+ ```
96
+ gem 'yardcheck'
97
+ ```
98
+
@@ -4,14 +4,33 @@ module Yardcheck
4
4
  class Const
5
5
  include Concord::Public.new(:constant)
6
6
 
7
+ SPECIAL_CASES = [
8
+ Hash,
9
+ Array
10
+ ].map { |const| [const.name, const] }.to_h
11
+
7
12
  def self.resolve(constant_name, scope = Object)
8
- return new(scope.const_get(constant_name)) if scope.const_defined?(constant_name)
13
+ if (const = direct_resolve(constant_name, scope))
14
+ const
15
+ else
16
+ resolve_parent(constant_name, scope)
17
+ end
18
+ end
9
19
 
20
+ def self.resolve_parent(constant_name, scope)
10
21
  parent = parent_namespace(scope)
11
22
  from_parent = resolve(constant_name, parent.constant) if parent.valid?
12
23
  from_parent && from_parent.valid? ? from_parent : Invalid.new(scope, constant_name)
13
24
  end
14
25
 
26
+ def self.direct_resolve(constant_name, scope)
27
+ if scope.equal?(Object) && constant_name.empty?
28
+ new(Object)
29
+ elsif scope.const_defined?(constant_name)
30
+ new(const_lookup(scope, constant_name))
31
+ end
32
+ end
33
+
15
34
  def self.parent_namespace(scope)
16
35
  parent_name = scope.name.split('::').slice(0...-1).join('::')
17
36
 
@@ -22,6 +41,13 @@ module Yardcheck
22
41
  end
23
42
  end
24
43
 
44
+ def self.const_lookup(scope, name)
45
+ SPECIAL_CASES.fetch(name) do
46
+ scope.const_get(name) if scope.const_defined?(name)
47
+ end
48
+ end
49
+ private_class_method :const_lookup
50
+
25
51
  def valid?
26
52
  true
27
53
  end
@@ -23,6 +23,10 @@ module Yardcheck
23
23
  return_typedef unless return_typedef&.invalid_const?
24
24
  end
25
25
 
26
+ def raise_type
27
+ raise_typedef unless raise_typedef&.invalid_const?
28
+ end
29
+
26
30
  def singleton?
27
31
  scope.equal?(:class)
28
32
  end
@@ -58,15 +62,29 @@ module Yardcheck
58
62
  [*param_warnings, *return_warning].map { |warning| Warning.new(self, warning) }
59
63
  end
60
64
 
65
+ def predicate_method?
66
+ selector.to_s.end_with?('?')
67
+ end
68
+
69
+ def processed_source
70
+ ProcessedSource.new(yardoc.source)
71
+ end
72
+ memoize :processed_source
73
+
61
74
  private
62
75
 
63
76
  def return_typedef
64
- return_tag.map(&method(:typedefs)).reduce(:+)
77
+ aggregate_tags(:return)
65
78
  end
66
79
  memoize :return_typedef
67
80
 
68
- def return_tag
69
- tags(:return)
81
+ def raise_typedef
82
+ aggregate_tags(:raise)
83
+ end
84
+ memoize :raise_typedef
85
+
86
+ def aggregate_tags(name)
87
+ tags(name).map(&method(:typedefs)).reduce(:+)
70
88
  end
71
89
 
72
90
  def param_typedefs
@@ -2,25 +2,21 @@
2
2
 
3
3
  module Yardcheck
4
4
  class MethodCall
5
- include Anima.new(
5
+ include AbstractType, Anima.new(
6
6
  :scope,
7
7
  :selector,
8
8
  :namespace,
9
9
  :params,
10
- :example_location,
11
- :return_value,
12
- :error_raised
10
+ :example_location
13
11
  )
14
12
 
15
- def self.process(params:, return_value:, **attributes)
13
+ def self.process(params:, **attributes)
16
14
  params =
17
15
  params.map do |key, value|
18
16
  [key, TestValue.process(value)]
19
17
  end.to_h
20
18
 
21
- return_value = TestValue.process(return_value)
22
-
23
- new(params: params, return_value: return_value, **attributes)
19
+ new(params: params, **attributes)
24
20
  end
25
21
 
26
22
  def method_identifier
@@ -31,8 +27,39 @@ module Yardcheck
31
27
  selector == :initialize && scope == :instance
32
28
  end
33
29
 
34
- def raised?
35
- error_raised
30
+ def raise?
31
+ false
32
+ end
33
+
34
+ def return?
35
+ false
36
36
  end
37
+
38
+ class Return < self
39
+ include anima.add(:return_value)
40
+
41
+ def self.process(return_value:, **kwargs)
42
+ super(return_value: TestValue.process(return_value), **kwargs)
43
+ end
44
+
45
+ def return?
46
+ true
47
+ end
48
+ end # Return
49
+
50
+ class Raise < self
51
+ include anima.add(:exception)
52
+
53
+ def self.process(exception:, **kwargs)
54
+ super(exception: TestValue.process(exception), **kwargs)
55
+ end
56
+
57
+ def raise?
58
+ true
59
+ end
60
+ end # Raise
61
+
62
+ class Jump < self
63
+ end # Jump
37
64
  end # MethodCall
38
65
  end # Yardcheck
@@ -6,6 +6,17 @@ module Yardcheck
6
6
 
7
7
  def initialize(namespace)
8
8
  super(namespace, [], [])
9
+
10
+ # When an exception is raised it isn't clear when it has been eventually rescued.
11
+ # We set `@current_exception` to `true` when we have observed an exception
12
+ # and have not seen a non `nil` return.
13
+ @current_exception = nil
14
+
15
+ # A block passed from one method down to another method can trigger a return of the
16
+ # originating method. This triggers a waterfall of `nil` returns because each method
17
+ # shortcircuits. Similar to `current_exception` we need to also track jumps triggered
18
+ # by block returns
19
+ @block_jump = false
9
20
  end
10
21
 
11
22
  def trace(&block)
@@ -18,8 +29,10 @@ module Yardcheck
18
29
 
19
30
  private
20
31
 
32
+ attr_reader :current_exception, :block_jump
33
+
21
34
  def tracer
22
- TracePoint.new(:call, :return, :raise) do |event|
35
+ TracePoint.new(:call, :return, :raise, :b_return) do |event|
23
36
  tracer.disable do
24
37
  process(event) if target?(event.defined_class)
25
38
  end
@@ -29,13 +42,18 @@ module Yardcheck
29
42
 
30
43
  def process(trace_event)
31
44
  case trace_event.event
32
- when :call then process_call(trace_event)
33
- when :return then process_return(trace_event)
34
- when :raise then process_raise
45
+ when :call then process_call(trace_event)
46
+ when :return then process_return(trace_event)
47
+ when :raise then process_raise(trace_event)
48
+ when :b_return then process_block_return
35
49
  end
36
50
  end
37
51
 
38
52
  def process_call(trace_event)
53
+ # If we observe a method call then we are certainly no longer inside of a
54
+ # bubbling up of early returns
55
+ @block_jump = false
56
+
39
57
  parameter_names =
40
58
  trace_event
41
59
  .defined_class
@@ -54,13 +72,35 @@ module Yardcheck
54
72
  end
55
73
 
56
74
  def process_return(trace_event)
57
- seen << MethodCall.process(
58
- call_stack.pop.merge(return_value: trace_event.return_value)
59
- )
75
+ return_value = trace_event.return_value
76
+
77
+ # If we observe a non `nil` return value then it is no longer possible that we are observing
78
+ # "fake returns" caused by either an exception being raised or a block being executed
79
+ # that invoked `return` and caused other methods to return early
80
+ unless nil.equal?(return_value)
81
+ @current_exception = nil
82
+ @block_jump = false
83
+ end
84
+
85
+ frame = call_stack.pop
86
+ method_call =
87
+ if @current_exception
88
+ MethodCall::Raise.process(frame.merge(exception: @current_exception))
89
+ elsif @block_jump
90
+ MethodCall::Jump.process(frame)
91
+ else
92
+ MethodCall::Return.process(frame.merge(return_value: return_value))
93
+ end
94
+
95
+ seen << method_call
96
+ end
97
+
98
+ def process_raise(trace_event)
99
+ @current_exception = trace_event.raised_exception
60
100
  end
61
101
 
62
- def process_raise
63
- call_stack.last[:error_raised] = true
102
+ def process_block_return
103
+ @block_jump = true
64
104
  end
65
105
 
66
106
  def event_details(event)
@@ -68,8 +108,7 @@ module Yardcheck
68
108
  scope: event.defined_class.__send__(:singleton_class?) ? :class : :instance,
69
109
  selector: event.method_id,
70
110
  namespace: event.defined_class,
71
- example_location: RSpec.current_example.location,
72
- error_raised: false
111
+ example_location: RSpec.current_example.location
73
112
  }
74
113
  end
75
114
 
@@ -5,7 +5,7 @@ module Yardcheck
5
5
  include Concord.new(:documentation, :event)
6
6
 
7
7
  def violations
8
- param_violations + return_violations
8
+ param_violations + return_violations + raise_violations
9
9
  end
10
10
 
11
11
  def source_code
@@ -36,10 +36,18 @@ module Yardcheck
36
36
  documentation.return_type
37
37
  end
38
38
 
39
+ def documented_raise_type
40
+ documentation.raise_type
41
+ end
42
+
39
43
  def actual_return_type
40
44
  event.return_value.type
41
45
  end
42
46
 
47
+ def actual_raise_type
48
+ event.exception.type
49
+ end
50
+
43
51
  def documentation_warnings
44
52
  documentation.warnings
45
53
  end
@@ -59,14 +67,32 @@ module Yardcheck
59
67
  end
60
68
 
61
69
  def return_violations
62
- invalid_return_type? ? [Violation::Return.new(self)] : []
70
+ invalid_return? ? [Violation::Return.new(self)] : []
71
+ end
72
+
73
+ def raise_violations
74
+ invalid_raise? ? [Violation::Raise.new(self)] : []
63
75
  end
64
76
 
65
- def invalid_return_type?
66
- documentation.return_type &&
77
+ def invalid_raise?
78
+ documentation.raise_type &&
79
+ event.raise? &&
80
+ !documentation.raise_type.match?(event.exception)
81
+ end
82
+
83
+ def invalid_return?
84
+ documentation.return_type &&
85
+ event.return? &&
67
86
  !documentation.return_type.match?(event.return_value) &&
68
- !event.raised? &&
69
- !event.initialize?
87
+ !event.initialize? &&
88
+ !documentation.predicate_method? &&
89
+ !possible_tracepoint_bug?
90
+ end
91
+
92
+ def possible_tracepoint_bug?
93
+ return false unless event.return_value.is?(nil)
94
+
95
+ documentation.processed_source.tracepoint_bug_candidate?
70
96
  end
71
97
  end # Observation
72
98
  end # Yardcheck
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yardcheck
4
+ class ProcessedSource
5
+ include Concord.new(:raw_source), Adamantium::Flat
6
+
7
+ # @see https://bugs.ruby-lang.org/issues/13369
8
+ def tracepoint_bug_candidate?
9
+ resbodies = find_resbodies
10
+ resbodies.any? do |node|
11
+ find_type(node, :return).any?
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def find_resbodies(node = ast)
18
+ find_type(node, :resbody)
19
+ end
20
+
21
+ def find_type(node, type)
22
+ return nil unless node.is_a?(Parser::AST::Node)
23
+ return [node] if node.type == type
24
+
25
+ node.children.map { |child| find_type(child, type) }.flatten.compact
26
+ end
27
+
28
+ def ast
29
+ Parser::CurrentRuby.parse(raw_source)
30
+ end
31
+ end # ProcessedSource
32
+ end # Yardcheck
@@ -53,6 +53,12 @@ module Yardcheck
53
53
  def check
54
54
  warn_all(warnings)
55
55
  warn_all(offenses)
56
+
57
+ if offenses.any?
58
+ Kernel.exit(1)
59
+ else
60
+ Kernel.exit(0)
61
+ end
56
62
  end
57
63
 
58
64
  private
@@ -76,7 +76,7 @@ module Yardcheck
76
76
  def signature
77
77
  type
78
78
  end
79
- end
79
+ end # Invalid
80
80
  end # Parser
81
81
  end # Typedef
82
82
  end # Yardcheck
@@ -5,7 +5,9 @@ module Yardcheck
5
5
  include Concord.new(:types)
6
6
 
7
7
  def self.parse(types)
8
- if types.include?(:undefined)
8
+ if types.empty?
9
+ Unspecified.new
10
+ elsif types.include?(:undefined)
9
11
  fail 'Cannot combined [undefined] with other types' unless types.one?
10
12
  Undefined.new
11
13
  elsif types.grep(Parser::Invalid).any?
@@ -33,6 +35,18 @@ module Yardcheck
33
35
  types.any?(&:invalid_const?)
34
36
  end
35
37
 
38
+ class Unspecified < self
39
+ include Concord.new
40
+
41
+ def signature
42
+ '(Unspecified type)'
43
+ end
44
+
45
+ def invalid_const?
46
+ true
47
+ end
48
+ end # Unspecified
49
+
36
50
  class Literal < self
37
51
  include Concord.new(:const)
38
52
 
@@ -117,6 +131,6 @@ module Yardcheck
117
131
  def invalid_const?
118
132
  true
119
133
  end
120
- end
134
+ end # Invalid
121
135
  end # Typedef
122
136
  end # Yardcheck
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yardcheck
4
- VERSION = '0.0.1'
4
+ VERSION = '0.0.2'
5
5
  end # Yardcheck
@@ -152,5 +152,43 @@ module Yardcheck
152
152
  observation.documented_param(name)
153
153
  end
154
154
  end # Param
155
+
156
+ class Raise < self
157
+ include Equalizer.new(:observation)
158
+
159
+ FORMAT =
160
+ "Expected #{blue('%<shorthand>s')} to raise " \
161
+ "#{yellow('%<signature>s')} but observed " \
162
+ "#{red('%<observed_type>s')}"
163
+
164
+ def initialize(observation, test_locations = [observation.test_location])
165
+ super
166
+ end
167
+
168
+ def explanation
169
+ format(
170
+ FORMAT,
171
+ shorthand: shorthand,
172
+ signature: signature,
173
+ observed_type: observed_type
174
+ )
175
+ end
176
+
177
+ protected
178
+
179
+ def observed_type
180
+ observation.actual_raise_type
181
+ end
182
+
183
+ private
184
+
185
+ def combine_requirements
186
+ %i[shorthand signature observed_type]
187
+ end
188
+
189
+ def expected_type
190
+ observation.documented_raise_type
191
+ end
192
+ end # Raise
155
193
  end # Violation
156
194
  end # Yardcheck
data/lib/yardcheck.rb CHANGED
@@ -5,6 +5,7 @@ require 'concord'
5
5
  require 'yard'
6
6
  require 'rspec'
7
7
  require 'coderay'
8
+ require 'parser/current'
8
9
 
9
10
  require 'yardcheck/version'
10
11
  require 'yardcheck/runner'
@@ -24,3 +25,4 @@ require 'yardcheck/color'
24
25
  require 'yardcheck/violation'
25
26
  require 'yardcheck/warning'
26
27
  require 'yardcheck/source_lines'
28
+ require 'yardcheck/processed_source'
@@ -33,9 +33,11 @@ RSpec.describe 'test app integration' do
33
33
  end
34
34
 
35
35
  it 'generates a warning for invalid constant' do
36
- expect_report('WARNING: Unabled to resolve "What" for lib/test_app.rb:37')
37
- expect_report('WARNING: Unabled to resolve "Wow" for lib/test_app.rb:37')
38
- expect_report('WARNING: Unabled to resolve :foo for lib/test_app.rb:109')
36
+ expect_report('WARNING: Unabled to resolve "What" for lib/test_app.rb:43')
37
+ expect_report('WARNING: Unabled to resolve "Wow" for lib/test_app.rb:43')
38
+ expect_report('WARNING: Unabled to resolve (Unspecified type) for lib/test_app.rb:58')
39
+ expect_report('WARNING: Unabled to resolve (Unspecified type) for lib/test_app.rb:58')
40
+ expect_report('WARNING: Unabled to resolve :foo for lib/test_app.rb:122')
39
41
  end
40
42
 
41
43
  it 'reports expectations' do
@@ -50,6 +52,10 @@ RSpec.describe 'test app integration' do
50
52
  'Expected TestApp::Namespace#improperly_tested_with_instance_double ' \
51
53
  'to receive String for value but observed Integer'
52
54
  )
55
+ expect_report(
56
+ 'Expected TestApp::Namespace#invalid_raise_documentation to raise ' \
57
+ 'TestApp::Namespace::AppError but observed KeyError'
58
+ )
53
59
  matches = report.scan(/^Expected .+ to return .+ but observed .+$/)
54
60
  expect(matches.size).to be(3)
55
61
  end
data/spec/spec_helper.rb CHANGED
@@ -4,7 +4,11 @@ require 'pathname'
4
4
  require 'bundler'
5
5
  require 'timeout'
6
6
 
7
- Bundler.with_clean_env { system('cd test_app && yard --no-cache --no-output > /dev/null') }
7
+ Bundler.with_clean_env do
8
+ Dir.chdir('test_app') do
9
+ YARD::CLI::Yardoc.run(*%w[--no-cache --no-output --no-stats --quiet --no-progress .])
10
+ end
11
+ end
8
12
 
9
13
  begin
10
14
  require 'mutest'