yard-doctest 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +6 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +286 -0
- data/Rakefile +8 -0
- data/features/support/env.rb +2 -0
- data/features/yard-doctest.feature +429 -0
- data/lib/yard/cli/doctest.rb +86 -0
- data/lib/yard/doctest/example.rb +53 -0
- data/lib/yard/doctest/rake.rb +34 -0
- data/lib/yard/doctest/version.rb +5 -0
- data/lib/yard-doctest.rb +29 -0
- data/yard-doctest.gemspec +27 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eaf710b790298916a7621aa89f1ccfc8f958d4a8
|
4
|
+
data.tar.gz: 3170484780f834944b8b2b23b4fd845d96b3f83b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 39ad0fa507a041b04f5d040a5609f1269ac38c8780cd3e4868a5a4d608babeafb289857b09ada303ddd0ed414e34af82fde8b8a253483493c2f422d012448441
|
7
|
+
data.tar.gz: 5a3d8827d8da6d84f690f51fb1374fb42171254b320a20db767bbbefe7b45cb355178331dbbb8c026fd826dc5c3f82833b2ba5ae18617d88e0b5687e8a26f461
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Alex Rodionov
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
# yard-doctest [](http://badge.fury.io/rb/yard-doctest) [](https://travis-ci.org/p0deje/yard-doctest)
|
2
|
+
|
3
|
+
Have you ever wanted to turn your amazing code examples into something that really make sense, is always up-to-date and bullet-proof? Were looking at an amazing [Python doctest](https://docs.python.org/3/library/doctest.html)? Well, look no longer!
|
4
|
+
|
5
|
+
Meet `YARD::Doctest` - simple and magical gem, which automatically parses your `@example` tags and turn them into tests!
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'yardoctest'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
$ bundle install
|
19
|
+
```
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
```bash
|
24
|
+
$ gem install yard-doctest
|
25
|
+
```
|
26
|
+
|
27
|
+
## Basic usage
|
28
|
+
|
29
|
+
Let's imagine you have the following library:
|
30
|
+
|
31
|
+
```
|
32
|
+
lib/
|
33
|
+
cat.rb
|
34
|
+
dog.rb
|
35
|
+
```
|
36
|
+
|
37
|
+
Each file contains some class and methods:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# cat.rb
|
41
|
+
class Cat
|
42
|
+
# @example
|
43
|
+
# Cat.word #=> 'meow'
|
44
|
+
def self.word
|
45
|
+
'meow'
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(can_hunt_dogs = false)
|
49
|
+
@can_hunt_dogs = can_hunt_dogs
|
50
|
+
end
|
51
|
+
|
52
|
+
# @example Usual cat cannot hunt dogs
|
53
|
+
# cat = Cat.new
|
54
|
+
# cat.can_hunt_dogs? #=> false
|
55
|
+
#
|
56
|
+
# @example Lion can hunt dogs
|
57
|
+
# cat = Cat.new(true)
|
58
|
+
# cat.can_hunt_dogs? #=> true
|
59
|
+
#
|
60
|
+
# @example Mutated cat can hunt dogs too
|
61
|
+
# cat = Cat.new
|
62
|
+
# cat.instance_variable_set(:@can_hunt_dogs, true) # not part of public API
|
63
|
+
# cat.can_hunt_dogs? #=> true
|
64
|
+
def can_hunt_dogs?
|
65
|
+
@can_hunt_dogs
|
66
|
+
end
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# dog.rb
|
72
|
+
class Dog
|
73
|
+
# @example
|
74
|
+
# Dog.word #=> 'meow'
|
75
|
+
def self.word
|
76
|
+
'woof'
|
77
|
+
end
|
78
|
+
|
79
|
+
# @example Dogs never hunt dogs
|
80
|
+
# dog = Dog.new
|
81
|
+
# dog.can_hunt_dogs? #=> false
|
82
|
+
def can_hunt_dogs?
|
83
|
+
false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
You can run tests for all the examples you've documented.
|
89
|
+
|
90
|
+
First of all, you need to tell YARD to automatically load `yard-doctest` (as well as other plugins):
|
91
|
+
|
92
|
+
```bash
|
93
|
+
$ bundle exec yard config load_plugins true
|
94
|
+
# if you don't want to load other plugins
|
95
|
+
$ bundle exec yard config -a autoload_plugins yard-doctest
|
96
|
+
```
|
97
|
+
|
98
|
+
Next, you'll need to create test helper, which will be required before each of your test. Think about it as `spec_helper.rb` in RSpec or `env.rb` in Cucumber. You should require everything necessary for your examples to run there.
|
99
|
+
|
100
|
+
```bash
|
101
|
+
$ touch yard-doctest_helper.rb
|
102
|
+
```
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
# yard-doctest_helper.rb
|
106
|
+
require 'lib/cat'
|
107
|
+
require 'lib/dog'
|
108
|
+
```
|
109
|
+
|
110
|
+
That's pretty much it, you can now run your examples:
|
111
|
+
|
112
|
+
```bash
|
113
|
+
$ bundle exec yard doctest
|
114
|
+
Run options: --seed 5974
|
115
|
+
|
116
|
+
# Running:
|
117
|
+
|
118
|
+
..F...
|
119
|
+
|
120
|
+
Finished in 0.015488s, 387.3967 runs/s, 387.3967 assertions/s.
|
121
|
+
|
122
|
+
1) Failure:
|
123
|
+
Dog.word#test_0001_ [lib/dog.rb:5]:
|
124
|
+
Expected: "meow"
|
125
|
+
Actual: "woof"
|
126
|
+
|
127
|
+
6 runs, 6 assertions, 1 failures, 0 errors, 0 skips
|
128
|
+
```
|
129
|
+
|
130
|
+
Oops, let's go back and fix the example by change "meow" to "woof" in `Dog.word` and re-run the examples:
|
131
|
+
|
132
|
+
```bash
|
133
|
+
$ sed -i.bak s/meow/woof/g lib/dog.rb
|
134
|
+
$ bundle exec yard doctest
|
135
|
+
Run options: --seed 51966
|
136
|
+
|
137
|
+
# Running:
|
138
|
+
|
139
|
+
......
|
140
|
+
|
141
|
+
Finished in 0.002712s, 2212.3894 runs/s, 2212.3894 assertions/s.
|
142
|
+
|
143
|
+
6 runs, 6 assertions, 0 failures, 0 errors, 0 skips
|
144
|
+
```
|
145
|
+
|
146
|
+
Pretty simple, ain't it? Need more details about the way it parses examples?
|
147
|
+
|
148
|
+
Think about `#=>` as equality assertion: everything before is actual result, everything after is expected result and they are asserted using `#==`.
|
149
|
+
|
150
|
+
You can use as many assertions as you want in a single example:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
class Cat
|
154
|
+
# @example
|
155
|
+
# cat = Cat.new
|
156
|
+
# cat.can_hunt_dogs? #=> false
|
157
|
+
# cat = Cat.new(true)
|
158
|
+
# cat.can_hunt_dogs? #=> true
|
159
|
+
def can_hunt_dogs?
|
160
|
+
@can_hunt_dogs
|
161
|
+
end
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
In this case, example will be run as a single test but with multiple assertions:
|
166
|
+
|
167
|
+
```bash
|
168
|
+
$ bundle exec yard doctest lib/cat.rb
|
169
|
+
# ...
|
170
|
+
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
|
171
|
+
```
|
172
|
+
|
173
|
+
If your example has no assertions, it will still be evaluated to ensure nothing is raised at least:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
class Cat
|
177
|
+
# @example
|
178
|
+
# cat = Cat.new
|
179
|
+
# cat.can_hunt_dogs?
|
180
|
+
def can_hunt_dogs?
|
181
|
+
@can_hunt_dogs
|
182
|
+
end
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
```bash
|
187
|
+
$ bundle exec yard doctest lib/cat.rb
|
188
|
+
# ...
|
189
|
+
1 runs, 0 assertions, 0 failures, 0 errors, 0 skips
|
190
|
+
```
|
191
|
+
|
192
|
+
Pretty simple, ain't it? Need more details about the way it runs the tests?
|
193
|
+
|
194
|
+
It is actually delegated to amazing [minitest](https://github.com/seattlerb/minitest) and each example is an instance of `Minitest::Spec`.
|
195
|
+
|
196
|
+
## Advanced usage
|
197
|
+
|
198
|
+
You can define any methods and instance variables in test helper and they will be available in examples.
|
199
|
+
|
200
|
+
For example, if we change the examples for `Cat#can_hunt_dogs?` like that:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
# cat.rb
|
204
|
+
class Cat
|
205
|
+
# @example Usual cat cannot hunt dogs
|
206
|
+
# cat.can_hunt_dogs? #=> false
|
207
|
+
def can_hunt_dogs?
|
208
|
+
@can_hunt_dogs
|
209
|
+
end
|
210
|
+
end
|
211
|
+
```
|
212
|
+
|
213
|
+
And run the examples - it will fail because `cat is undefined`:
|
214
|
+
|
215
|
+
```bash
|
216
|
+
$ bundle exec yard doctest
|
217
|
+
# ...
|
218
|
+
1) Error:
|
219
|
+
Cat#can_hunt_dogs?#test_0001_Usual cat cannot hunt dogs:
|
220
|
+
NameError: undefined local variable or method `cat' for Object:Class
|
221
|
+
# ...
|
222
|
+
```
|
223
|
+
|
224
|
+
If you don't want to create new instance of class each time (or include module if you're testing it), you can fix this by defining a method in test helper:
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
# yard-doctest_helper.rb
|
228
|
+
require 'lib/cat'
|
229
|
+
require 'lib/dog'
|
230
|
+
|
231
|
+
def cat
|
232
|
+
@cat ||= Cat.new
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
In case you need to do some preparations/cleanup between tests, hooks are at your service to be defined in test helper:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
YARD::Doctest.before do
|
240
|
+
# this is called before each example and
|
241
|
+
# evaluated in the same context as example
|
242
|
+
# (i.e. has access to the same instance variables)
|
243
|
+
end
|
244
|
+
|
245
|
+
YARD::Doctest.after do
|
246
|
+
# same as `before`, but runs after each example
|
247
|
+
end
|
248
|
+
|
249
|
+
YARD::Doctest.after_run do
|
250
|
+
# runs after all the examples and
|
251
|
+
# has different context
|
252
|
+
# (i.e. no access to instance variables)
|
253
|
+
end
|
254
|
+
```
|
255
|
+
|
256
|
+
There is also a Rake task for you:
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
# Rakefile
|
260
|
+
require 'yard-doctest'
|
261
|
+
YARD::Doctest::RakeTask.new do |task|
|
262
|
+
task.doctest_opts = %w[-v]
|
263
|
+
task.pattern = 'lib/**/*.rb'
|
264
|
+
end
|
265
|
+
```
|
266
|
+
|
267
|
+
```bash
|
268
|
+
$ bundle exec rake yard:doctest
|
269
|
+
```
|
270
|
+
|
271
|
+
## Testing
|
272
|
+
|
273
|
+
There are some system tests implemented with [Aruba](https://github.com/cucumber/aruba):
|
274
|
+
|
275
|
+
```bash
|
276
|
+
$ bundle install
|
277
|
+
$ bundle exec rake cucumber
|
278
|
+
```
|
279
|
+
|
280
|
+
## Contributing
|
281
|
+
|
282
|
+
* Fork the project.
|
283
|
+
* Make your feature addition or bug fix.
|
284
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
285
|
+
* Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
286
|
+
* Send me a pull request. Bonus points for topic branches.
|
data/Rakefile
ADDED
@@ -0,0 +1,429 @@
|
|
1
|
+
Feature: yard doctest
|
2
|
+
In order to avoid creating separated from code tests
|
3
|
+
And to keep my code examples correct and meaningful
|
4
|
+
As a developer
|
5
|
+
I want to automatically parse YARD's @example tags
|
6
|
+
And use them as tests
|
7
|
+
Just like doctest in Python
|
8
|
+
|
9
|
+
Scenario: adds new command to yard
|
10
|
+
When I run `bundle exec yard --help`
|
11
|
+
Then the output should contain "doctest Doctests from @example tags"
|
12
|
+
|
13
|
+
Scenario: looks for files in app/lib directories by default
|
14
|
+
Given a file named "yard-doctest_helper.rb" with:
|
15
|
+
"""
|
16
|
+
require 'app/app'
|
17
|
+
require 'lib/lib'
|
18
|
+
"""
|
19
|
+
And a file named "app/app.rb" with:
|
20
|
+
"""
|
21
|
+
# @example
|
22
|
+
# sum(2, 2) #=> 4
|
23
|
+
def sum(one, two)
|
24
|
+
one + two
|
25
|
+
end
|
26
|
+
"""
|
27
|
+
And a file named "lib/lib.rb" with:
|
28
|
+
"""
|
29
|
+
# @example
|
30
|
+
# sub(2, 2) #=> 0
|
31
|
+
def sub(one, two)
|
32
|
+
one - two
|
33
|
+
end
|
34
|
+
"""
|
35
|
+
When I run `bundle exec yard doctest`
|
36
|
+
Then the output should contain "2 runs, 2 assertions, 0 failures, 0 errors, 0 skips"
|
37
|
+
|
38
|
+
Scenario Outline: looks for files only in passed glob
|
39
|
+
Given a file named "yard-doctest_helper.rb" with:
|
40
|
+
"""
|
41
|
+
require 'app/app'
|
42
|
+
require 'lib/lib'
|
43
|
+
"""
|
44
|
+
And a file named "app/app.rb" with:
|
45
|
+
"""
|
46
|
+
# @example
|
47
|
+
# sum(2, 2) #=> 4
|
48
|
+
def sum(one, two)
|
49
|
+
one + two
|
50
|
+
end
|
51
|
+
"""
|
52
|
+
And a file named "lib/lib.rb" with:
|
53
|
+
"""
|
54
|
+
# @example
|
55
|
+
# sub(2, 2) #=> 0
|
56
|
+
def sub(one, two)
|
57
|
+
one - two
|
58
|
+
end
|
59
|
+
"""
|
60
|
+
When I run `bundle exec yard doctest <glob>`
|
61
|
+
Then the output should contain "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips"
|
62
|
+
Examples:
|
63
|
+
| glob |
|
64
|
+
| app |
|
65
|
+
| app/*.rb |
|
66
|
+
| app/** |
|
67
|
+
| app/**/*.rb |
|
68
|
+
| app/app.rb |
|
69
|
+
|
70
|
+
Scenario: generates test names from unit name
|
71
|
+
Given a file named "yard-doctest_helper.rb" with:
|
72
|
+
"""
|
73
|
+
require 'app/app'
|
74
|
+
"""
|
75
|
+
And a file named "app/app.rb" with:
|
76
|
+
"""
|
77
|
+
# @example
|
78
|
+
# sum(2, 2) #=> 4
|
79
|
+
def sum(one, two)
|
80
|
+
one + two
|
81
|
+
end
|
82
|
+
|
83
|
+
module A
|
84
|
+
# @example
|
85
|
+
# sub(2, 2) #=> 0
|
86
|
+
def sub(one, two)
|
87
|
+
one - two
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class B
|
92
|
+
# @example
|
93
|
+
# B.multiply(3, 3) #=> 9
|
94
|
+
def self.multiply(one, two)
|
95
|
+
one * two
|
96
|
+
end
|
97
|
+
|
98
|
+
# @example
|
99
|
+
# div(9, 3) #=> 3
|
100
|
+
def div(one, two)
|
101
|
+
one / two
|
102
|
+
end
|
103
|
+
end
|
104
|
+
"""
|
105
|
+
When I run `bundle exec yard doctest -v`
|
106
|
+
Then the output should contain "#sum"
|
107
|
+
And the output should contain "A#sub"
|
108
|
+
And the output should contain "B.multiply"
|
109
|
+
And the output should contain "B#div"
|
110
|
+
|
111
|
+
Scenario: asserts using equality
|
112
|
+
Given a file named "yard-doctest_helper.rb" with:
|
113
|
+
"""
|
114
|
+
require 'app/app'
|
115
|
+
"""
|
116
|
+
And a file named "app/app.rb" with:
|
117
|
+
"""
|
118
|
+
# @example
|
119
|
+
# sum(1, 1) #=> 2
|
120
|
+
def sum(one, two)
|
121
|
+
(one + two).to_s
|
122
|
+
end
|
123
|
+
"""
|
124
|
+
When I run `bundle exec yard doctest`
|
125
|
+
Then the output should contain:
|
126
|
+
"""
|
127
|
+
Expected: 2
|
128
|
+
Actual: "2"
|
129
|
+
"""
|
130
|
+
|
131
|
+
Scenario Outline: properly handles different return values
|
132
|
+
Given a file named "yard-doctest_helper.rb" with:
|
133
|
+
"""
|
134
|
+
require 'app/app'
|
135
|
+
"""
|
136
|
+
And a file named "app/app.rb" with:
|
137
|
+
"""
|
138
|
+
# @example
|
139
|
+
# foo #=> <value>
|
140
|
+
def foo
|
141
|
+
<value>
|
142
|
+
end
|
143
|
+
"""
|
144
|
+
When I run `bundle exec yard doctest`
|
145
|
+
Then the output should contain "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips"
|
146
|
+
Examples:
|
147
|
+
| value |
|
148
|
+
| true |
|
149
|
+
| false |
|
150
|
+
| nil |
|
151
|
+
| '' |
|
152
|
+
| "" |
|
153
|
+
| [] |
|
154
|
+
| {} |
|
155
|
+
| Class |
|
156
|
+
| 0 |
|
157
|
+
| 10 |
|
158
|
+
| -1 |
|
159
|
+
| 1.0 |
|
160
|
+
|
161
|
+
Scenario: handles multiple @example tags
|
162
|
+
Given a file named "yard-doctest_helper.rb" with:
|
163
|
+
"""
|
164
|
+
require 'app/app'
|
165
|
+
"""
|
166
|
+
And a file named "app/app.rb" with:
|
167
|
+
"""
|
168
|
+
# @example
|
169
|
+
# sum(2, 2) #=> 4
|
170
|
+
# @example
|
171
|
+
# sum(3, 3) #=> 6
|
172
|
+
def sum(one, two)
|
173
|
+
one + two
|
174
|
+
end
|
175
|
+
"""
|
176
|
+
When I run `bundle exec yard doctest`
|
177
|
+
Then the output should contain "2 runs, 2 assertions, 0 failures, 0 errors, 0 skips"
|
178
|
+
|
179
|
+
Scenario: handles multiple return comments
|
180
|
+
Given a file named "yard-doctest_helper.rb" with:
|
181
|
+
"""
|
182
|
+
require 'app/app'
|
183
|
+
"""
|
184
|
+
And a file named "app/app.rb" with:
|
185
|
+
"""
|
186
|
+
# @example
|
187
|
+
# a = 1
|
188
|
+
# sum(a, 2) #=> 3
|
189
|
+
# sum(a, 3) #=> 4
|
190
|
+
def sum(one, two)
|
191
|
+
one + two
|
192
|
+
end
|
193
|
+
"""
|
194
|
+
When I run `bundle exec yard doctest`
|
195
|
+
Then the output should contain "1 runs, 2 assertions, 0 failures, 0 errors, 0 skips"
|
196
|
+
|
197
|
+
Scenario: runs @example tags without return comment
|
198
|
+
Given a file named "yard-doctest_helper.rb" with:
|
199
|
+
"""
|
200
|
+
require 'app/app'
|
201
|
+
"""
|
202
|
+
And a file named "app/app.rb" with:
|
203
|
+
"""
|
204
|
+
# @example
|
205
|
+
# sum(2, 2)
|
206
|
+
def sum(one, two)
|
207
|
+
one + two
|
208
|
+
end
|
209
|
+
"""
|
210
|
+
When I run `bundle exec yard doctest`
|
211
|
+
Then the output should contain "1 runs, 0 assertions, 0 failures, 0 errors, 0 skips"
|
212
|
+
|
213
|
+
Scenario: handles `# =>` return comment
|
214
|
+
Given a file named "yard-doctest_helper.rb" with:
|
215
|
+
"""
|
216
|
+
require 'app/app'
|
217
|
+
"""
|
218
|
+
And a file named "app/app.rb" with:
|
219
|
+
"""
|
220
|
+
# @example
|
221
|
+
# sum(2, 2) # => 4
|
222
|
+
def sum(one, two)
|
223
|
+
one + two
|
224
|
+
end
|
225
|
+
"""
|
226
|
+
When I run `bundle exec yard doctest`
|
227
|
+
Then the output should contain "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips"
|
228
|
+
|
229
|
+
Scenario: handles return comment on newline
|
230
|
+
Given a file named "yard-doctest_helper.rb" with:
|
231
|
+
"""
|
232
|
+
require 'app/app'
|
233
|
+
"""
|
234
|
+
And a file named "app/app.rb" with:
|
235
|
+
"""
|
236
|
+
# @example
|
237
|
+
# sum(2, 2)
|
238
|
+
# #=> 4
|
239
|
+
def sum(one, two)
|
240
|
+
one + two
|
241
|
+
end
|
242
|
+
"""
|
243
|
+
When I run `bundle exec yard doctest`
|
244
|
+
Then the output should contain "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips"
|
245
|
+
|
246
|
+
Scenario: handles multiple lines
|
247
|
+
Given a file named "yard-doctest_helper.rb" with:
|
248
|
+
"""
|
249
|
+
require 'app/app'
|
250
|
+
"""
|
251
|
+
And a file named "app/app.rb" with:
|
252
|
+
"""
|
253
|
+
# @example
|
254
|
+
# a = 1
|
255
|
+
# b = 2
|
256
|
+
# sum(a, b)
|
257
|
+
# #=> 3
|
258
|
+
def sum(one, two)
|
259
|
+
one + two
|
260
|
+
end
|
261
|
+
"""
|
262
|
+
When I run `bundle exec yard doctest`
|
263
|
+
Then the output should contain "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips"
|
264
|
+
|
265
|
+
Scenario: names test with example title when it's present
|
266
|
+
Given a file named "yard-doctest_helper.rb" with:
|
267
|
+
"""
|
268
|
+
require 'app/app'
|
269
|
+
"""
|
270
|
+
And a file named "app/app.rb" with:
|
271
|
+
"""
|
272
|
+
# @example sums two numbers
|
273
|
+
# sum(2, 2) #=> 4
|
274
|
+
def sum(one, two)
|
275
|
+
one + two
|
276
|
+
end
|
277
|
+
"""
|
278
|
+
When I run `bundle exec yard doctest -v`
|
279
|
+
Then the output should contain "#sum#test_0001_sums two numbers"
|
280
|
+
|
281
|
+
Scenario: doesn't name test when title is not present
|
282
|
+
Given a file named "yard-doctest_helper.rb" with:
|
283
|
+
"""
|
284
|
+
require 'app/app'
|
285
|
+
"""
|
286
|
+
And a file named "app/app.rb" with:
|
287
|
+
"""
|
288
|
+
# @example
|
289
|
+
# sum(2, 2) #=> 4
|
290
|
+
def sum(one, two)
|
291
|
+
one + two
|
292
|
+
end
|
293
|
+
"""
|
294
|
+
When I run `bundle exec yard doctest -v`
|
295
|
+
Then the output should contain "#sum#test_0001_"
|
296
|
+
|
297
|
+
Scenario: adds unit definition to backtrace on failures
|
298
|
+
Given a file named "yard-doctest_helper.rb" with:
|
299
|
+
"""
|
300
|
+
require 'app/app'
|
301
|
+
"""
|
302
|
+
And a file named "app/app.rb" with:
|
303
|
+
"""
|
304
|
+
# @example
|
305
|
+
# sum(2, 2) #=> 5
|
306
|
+
def sum(one, two)
|
307
|
+
one + two
|
308
|
+
end
|
309
|
+
"""
|
310
|
+
When I run `bundle exec yard doctest`
|
311
|
+
Then the output should contain "app/app.rb:3"
|
312
|
+
|
313
|
+
Scenario: has rake task to run the tests
|
314
|
+
Given a file named "yard-doctest_helper.rb" with:
|
315
|
+
"""
|
316
|
+
require 'app/app'
|
317
|
+
"""
|
318
|
+
And a file named "app/app.rb" with:
|
319
|
+
"""
|
320
|
+
# @example
|
321
|
+
# sum(2, 2) #=> 4
|
322
|
+
def sum(one, two)
|
323
|
+
one + two
|
324
|
+
end
|
325
|
+
"""
|
326
|
+
And a file named "Rakefile" with:
|
327
|
+
"""
|
328
|
+
require 'yard-doctest'
|
329
|
+
YARD::Doctest::RakeTask.new do |task|
|
330
|
+
task.doctest_opts = %w[-v]
|
331
|
+
task.pattern = 'app/**/*.rb'
|
332
|
+
end
|
333
|
+
"""
|
334
|
+
When I run `bundle exec rake yard:doctest`
|
335
|
+
Then the output should contain "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips"
|
336
|
+
|
337
|
+
Scenario: requires doctest helper
|
338
|
+
Given a file named "yard-doctest_helper.rb" with:
|
339
|
+
"""
|
340
|
+
require 'app/app'
|
341
|
+
|
342
|
+
def a
|
343
|
+
2
|
344
|
+
end
|
345
|
+
|
346
|
+
def b
|
347
|
+
2
|
348
|
+
end
|
349
|
+
"""
|
350
|
+
And a file named "app/app.rb" with:
|
351
|
+
"""
|
352
|
+
# @example
|
353
|
+
# sum(a, b) #=> 4
|
354
|
+
def sum(one, two)
|
355
|
+
one + two
|
356
|
+
end
|
357
|
+
"""
|
358
|
+
When I run `bundle exec yard doctest`
|
359
|
+
Then the output should contain "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips"
|
360
|
+
|
361
|
+
Scenario: shares binding between asserts
|
362
|
+
Given a file named "yard-doctest_helper.rb" with:
|
363
|
+
"""
|
364
|
+
require 'app/app'
|
365
|
+
"""
|
366
|
+
And a file named "app/app.rb" with:
|
367
|
+
"""
|
368
|
+
# @example
|
369
|
+
# a, b = 1, 2
|
370
|
+
# sum(a, b) #=> 3
|
371
|
+
# a = 2
|
372
|
+
# sum(a, b) #=> 4
|
373
|
+
def sum(one, two)
|
374
|
+
one + two
|
375
|
+
end
|
376
|
+
"""
|
377
|
+
When I run `bundle exec yard doctest`
|
378
|
+
Then the output should contain "1 runs, 2 assertions, 0 failures, 0 errors, 0 skips"
|
379
|
+
|
380
|
+
Scenario: does not share binding between examples
|
381
|
+
Given a file named "yard-doctest_helper.rb" with:
|
382
|
+
"""
|
383
|
+
require 'app/app'
|
384
|
+
"""
|
385
|
+
And a file named "app/app.rb" with:
|
386
|
+
"""
|
387
|
+
# @example
|
388
|
+
# a, b = 1, 2
|
389
|
+
# sum(a, b) #=> 3
|
390
|
+
#
|
391
|
+
# @example
|
392
|
+
# sum(a, b) #=> 4
|
393
|
+
def sum(one, two)
|
394
|
+
one + two
|
395
|
+
end
|
396
|
+
"""
|
397
|
+
When I run `bundle exec yard doctest`
|
398
|
+
Then the output should contain "NameError: undefined local variable or method `a'"
|
399
|
+
|
400
|
+
Scenario: supports hooks
|
401
|
+
Given a file named "yard-doctest_helper.rb" with:
|
402
|
+
"""
|
403
|
+
require 'app/app'
|
404
|
+
|
405
|
+
@flag = true
|
406
|
+
|
407
|
+
YARD::Doctest.before do
|
408
|
+
@flag = false
|
409
|
+
end
|
410
|
+
|
411
|
+
YARD::Doctest.after do
|
412
|
+
@flag = true
|
413
|
+
end
|
414
|
+
|
415
|
+
YARD::Doctest.after_run do
|
416
|
+
puts 'Run after all by minitest'
|
417
|
+
end
|
418
|
+
"""
|
419
|
+
And a file named "app/app.rb" with:
|
420
|
+
"""
|
421
|
+
# @example
|
422
|
+
# flag #=> false
|
423
|
+
def flag
|
424
|
+
@flag
|
425
|
+
end
|
426
|
+
"""
|
427
|
+
When I run `bundle exec yard doctest`
|
428
|
+
Then the output should contain "1 runs, 1 assertions, 0 failures, 0 errors, 0 skips"
|
429
|
+
And the output should contain "Run after all by minitest"
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module YARD
|
2
|
+
module CLI
|
3
|
+
class Doctest < Command
|
4
|
+
|
5
|
+
def description
|
6
|
+
'Doctests from @example tags'
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(*args)
|
10
|
+
files = args.select { |arg| arg !~ /^-/ }
|
11
|
+
|
12
|
+
files = parse_files(files)
|
13
|
+
examples = parse_examples(files)
|
14
|
+
|
15
|
+
add_pwd_to_path
|
16
|
+
require_helper
|
17
|
+
|
18
|
+
hooks = {}.tap do |hash|
|
19
|
+
hash[:before] = YARD::Doctest.before if YARD::Doctest.before.is_a?(Proc)
|
20
|
+
hash[:after] = YARD::Doctest.after if YARD::Doctest.after.is_a?(Proc)
|
21
|
+
end
|
22
|
+
|
23
|
+
generate_tests(examples, hooks)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def parse_files(globs)
|
29
|
+
globs = %w(app lib) if globs.empty?
|
30
|
+
|
31
|
+
files = globs.map do |glob|
|
32
|
+
if glob !~ /.rb$/
|
33
|
+
glob = "#{glob}/**/*.rb"
|
34
|
+
end
|
35
|
+
|
36
|
+
Dir[glob]
|
37
|
+
end
|
38
|
+
|
39
|
+
files.flatten
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_examples(files)
|
43
|
+
YARD.parse(files)
|
44
|
+
registry = Registry.load_all
|
45
|
+
registry.all.map { |object| object.tags(:example) }.flatten
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_tests(examples, hooks)
|
49
|
+
examples.each do |example|
|
50
|
+
path = example.object.path
|
51
|
+
file = "#{Dir.pwd}/#{example.object.files.first.join(':')}"
|
52
|
+
name = example.name
|
53
|
+
text = example.text
|
54
|
+
|
55
|
+
text = text.gsub('# =>', '#=>')
|
56
|
+
text = text.gsub('#=>', "\n#=>")
|
57
|
+
lines = text.split("\n").map(&:strip).reject(&:empty?)
|
58
|
+
|
59
|
+
asserts = [].tap do |arr|
|
60
|
+
until lines.empty?
|
61
|
+
actual = lines.take_while { |l| l !~ /^#=>/ }
|
62
|
+
expected = lines[actual.size] || ''
|
63
|
+
lines.slice! 0..actual.size
|
64
|
+
|
65
|
+
arr << {
|
66
|
+
expected: expected.sub('#=>', '').strip,
|
67
|
+
actual: actual.join("\n"),
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
YARD::Doctest::Example.new(path, file, name, asserts, hooks)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_pwd_to_path
|
77
|
+
$LOAD_PATH.unshift(Dir.pwd) unless $LOAD_PATH.include?(Dir.pwd)
|
78
|
+
end
|
79
|
+
|
80
|
+
def require_helper
|
81
|
+
require 'yard-doctest_helper'
|
82
|
+
end
|
83
|
+
|
84
|
+
end # Doctest
|
85
|
+
end # CLI
|
86
|
+
end # YARD
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'sourcify'
|
2
|
+
|
3
|
+
module YARD
|
4
|
+
module Doctest
|
5
|
+
class Example
|
6
|
+
|
7
|
+
#
|
8
|
+
# There are a bunch of hacks happening here:
|
9
|
+
#
|
10
|
+
# 1. Everything is done within constructor.
|
11
|
+
# 2. Context (binding) is shared between helper, example and hooks.
|
12
|
+
# 3. Since hooks are blocks, they can't be passed to `#eval`,
|
13
|
+
# so we translate them into string of Ruby code.
|
14
|
+
# 4. Intercept exception backtrace and add example object definition path.
|
15
|
+
#
|
16
|
+
|
17
|
+
def initialize(path, file, name, asserts, hooks)
|
18
|
+
Object.instance_eval do
|
19
|
+
context = binding
|
20
|
+
|
21
|
+
require 'minitest/autorun'
|
22
|
+
context.eval "require 'yard-doctest_helper'"
|
23
|
+
|
24
|
+
describe path do
|
25
|
+
before { context.eval(hooks[:before].to_source(strip_enclosure: true)) } if hooks[:before]
|
26
|
+
after { context.eval(hooks[:after].to_source(strip_enclosure: true)) } if hooks[:after]
|
27
|
+
|
28
|
+
it name do
|
29
|
+
asserts.each do |assert|
|
30
|
+
expected, actual = assert[:expected], assert[:actual]
|
31
|
+
actual = context.eval(actual)
|
32
|
+
|
33
|
+
unless expected.empty?
|
34
|
+
begin
|
35
|
+
assert_equal context.eval(expected), actual
|
36
|
+
rescue Minitest::Assertion => e
|
37
|
+
backtrace = e.backtrace
|
38
|
+
example = backtrace.find { |trace| trace =~ %r(lib/yard/doctest/example) }
|
39
|
+
example = backtrace.index(example)
|
40
|
+
backtrace = backtrace.insert(example, file)
|
41
|
+
e.set_backtrace backtrace
|
42
|
+
raise e
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end # Example
|
52
|
+
end # Doctest
|
53
|
+
end # YARD
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
|
4
|
+
module YARD
|
5
|
+
module Doctest
|
6
|
+
class RakeTask < ::Rake::TaskLib
|
7
|
+
|
8
|
+
attr_accessor :name
|
9
|
+
attr_accessor :doctest_opts
|
10
|
+
attr_accessor :pattern
|
11
|
+
|
12
|
+
def initialize(name = 'yard:doctest')
|
13
|
+
@name = name
|
14
|
+
@doctest_opts = []
|
15
|
+
@pattern = ''
|
16
|
+
|
17
|
+
yield self if block_given?
|
18
|
+
|
19
|
+
define
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def define
|
25
|
+
desc 'Run YARD doctests'
|
26
|
+
task(name) do
|
27
|
+
command = "yard doctest #{(doctest_opts << pattern).join(' ')}"
|
28
|
+
system(command)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end # RakeTask
|
33
|
+
end # Doctest
|
34
|
+
end # YARD
|
data/lib/yard-doctest.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'yard'
|
2
|
+
require 'minitest'
|
3
|
+
|
4
|
+
require 'yard/cli/doctest'
|
5
|
+
require 'yard/doctest/example'
|
6
|
+
require 'yard/doctest/rake'
|
7
|
+
require 'yard/doctest/version'
|
8
|
+
|
9
|
+
module YARD
|
10
|
+
module Doctest
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def before(&blk)
|
14
|
+
block_given? ? @before = blk : @before
|
15
|
+
end
|
16
|
+
|
17
|
+
def after(&blk)
|
18
|
+
block_given? ? @after = blk : @after
|
19
|
+
end
|
20
|
+
|
21
|
+
def after_run(&blk)
|
22
|
+
Minitest.after_run &blk
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end # Doctest
|
27
|
+
end # YARD
|
28
|
+
|
29
|
+
YARD::CLI::CommandParser.commands[:doctest] = YARD::CLI::Doctest
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'yard/doctest/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'yard-doctest'
|
8
|
+
spec.version = YARD::Doctest::VERSION
|
9
|
+
spec.author = 'Alex Rodionov'
|
10
|
+
spec.email = 'p0deje@gmail.com'
|
11
|
+
spec.summary = 'Doctests from YARD examples'
|
12
|
+
spec.description = 'Execute YARD examples as tests'
|
13
|
+
spec.homepage = 'https://github.com/p0deje/yard-doctest'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.test_files = spec.files.grep(/^features\//)
|
18
|
+
spec.require_path = 'lib'
|
19
|
+
|
20
|
+
spec.add_runtime_dependency 'yard'
|
21
|
+
spec.add_runtime_dependency 'minitest'
|
22
|
+
spec.add_runtime_dependency 'sourcify'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler'
|
25
|
+
spec.add_development_dependency 'rake'
|
26
|
+
spec.add_development_dependency 'aruba'
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: yard-doctest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex Rodionov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yard
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sourcify
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: aruba
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Execute YARD examples as tests
|
98
|
+
email: p0deje@gmail.com
|
99
|
+
executables: []
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- ".gitignore"
|
104
|
+
- ".travis.yml"
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE.txt
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- features/support/env.rb
|
110
|
+
- features/yard-doctest.feature
|
111
|
+
- lib/yard-doctest.rb
|
112
|
+
- lib/yard/cli/doctest.rb
|
113
|
+
- lib/yard/doctest/example.rb
|
114
|
+
- lib/yard/doctest/rake.rb
|
115
|
+
- lib/yard/doctest/version.rb
|
116
|
+
- yard-doctest.gemspec
|
117
|
+
homepage: https://github.com/p0deje/yard-doctest
|
118
|
+
licenses:
|
119
|
+
- MIT
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.2.0
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: Doctests from YARD examples
|
141
|
+
test_files:
|
142
|
+
- features/support/env.rb
|
143
|
+
- features/yard-doctest.feature
|
144
|
+
has_rdoc:
|