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 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: