yard-doctest 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - 2.1.0
5
+
6
+ before_script: bundle exec yard config -a autoload_plugins yard-doctest
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
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 [![Gem Version](https://badge.fury.io/rb/yard-doctest.png)](http://badge.fury.io/rb/yard-doctest) [![Build Status](https://travis-ci.org/p0deje/yard-doctest.png?branch=master)](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,8 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'cucumber/rake/task'
4
+ Cucumber::Rake::Task.new do |task|
5
+ task.cucumber_opts = '-f progress features'
6
+ end
7
+
8
+ task default: :cucumber
@@ -0,0 +1,2 @@
1
+ require 'yard-doctest'
2
+ require 'aruba/cucumber'
@@ -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
@@ -0,0 +1,5 @@
1
+ module YARD
2
+ module Doctest
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -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: