ruote-resque 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 +15 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.rubocop.yml +4 -0
- data/.travis.yml +14 -0
- data/.yardopts +4 -0
- data/Gemfile +13 -0
- data/LICENSE +22 -0
- data/README.md +156 -0
- data/Rakefile +1 -0
- data/lib/ruote/resque.rb +40 -0
- data/lib/ruote/resque/client.rb +62 -0
- data/lib/ruote/resque/job.rb +47 -0
- data/lib/ruote/resque/participant.rb +86 -0
- data/lib/ruote/resque/participant_registrar.rb +42 -0
- data/lib/ruote/resque/receiver.rb +140 -0
- data/lib/ruote/resque/reply_job.rb +23 -0
- data/lib/ruote/resque/version.rb +7 -0
- data/ruote-resque.gemspec +28 -0
- data/spec/lib/ruote/resque/job_spec.rb +129 -0
- data/spec/lib/ruote/resque/participant_registrar_spec.rb +52 -0
- data/spec/lib/ruote/resque/participant_spec.rb +118 -0
- data/spec/lib/ruote/resque/receiver_spec.rb +208 -0
- data/spec/lib/ruote/resque/reply_job_spec.rb +38 -0
- data/spec/lib/ruote/resque_spec.rb +99 -0
- data/spec/spec_helper.rb +21 -0
- metadata +161 -0
@@ -0,0 +1,208 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ruote/storage/fs_storage'
|
5
|
+
|
6
|
+
class BravoJob
|
7
|
+
@queue = :rspec
|
8
|
+
extend Ruote::Resque::Job
|
9
|
+
def self.perform(workitem)
|
10
|
+
workitem['fields']['resque_bravo'] = 'was here'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class BravoError < RuntimeError
|
15
|
+
end
|
16
|
+
|
17
|
+
class BravoFailureJob < BravoJob
|
18
|
+
@queue = :rspec
|
19
|
+
extend Ruote::Resque::Job
|
20
|
+
def self.perform(workitem)
|
21
|
+
raise BravoError, 'im a failure'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
RUOTE_WAIT_TIMEOUT = 10
|
26
|
+
|
27
|
+
describe Ruote::Resque::Receiver do
|
28
|
+
|
29
|
+
before :each do
|
30
|
+
|
31
|
+
Resque.redis.flushdb
|
32
|
+
|
33
|
+
@board = Ruote::Dashboard.new(Ruote::Worker.new(Ruote::HashStorage.new))
|
34
|
+
# @board.noisy = true
|
35
|
+
|
36
|
+
@board.register(/^block_/) do |workitem|
|
37
|
+
workitem.fields[workitem.participant_name] = 'was here'
|
38
|
+
end
|
39
|
+
|
40
|
+
@worker = Thread.new do
|
41
|
+
queues = ['rspec']
|
42
|
+
worker = Resque::Worker.new(*queues)
|
43
|
+
worker.term_timeout = 4
|
44
|
+
worker.term_child = true
|
45
|
+
# worker.verbose = true
|
46
|
+
worker.work(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
Ruote::Resque.configure do |config|
|
50
|
+
config.interval = 1
|
51
|
+
end
|
52
|
+
|
53
|
+
@receiver = Ruote::Resque::Receiver.new(@board)
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
after :each do
|
58
|
+
|
59
|
+
@board.shutdown
|
60
|
+
@board.storage.purge!
|
61
|
+
@receiver.shutdown
|
62
|
+
@worker.kill
|
63
|
+
|
64
|
+
Ruote::Resque.configure do |config|
|
65
|
+
config.interval = 5
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
let(:definition) do
|
70
|
+
Ruote.define :on_error => 'block_delta' do
|
71
|
+
block_alpha
|
72
|
+
resque_bravo
|
73
|
+
block_charly
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'invalid job handling' do
|
78
|
+
|
79
|
+
it 'raises InvalidJob' do
|
80
|
+
|
81
|
+
class AnotherJob
|
82
|
+
end
|
83
|
+
|
84
|
+
Ruote::Resque::Receiver.any_instance.should_receive(:handle_error) do |e|
|
85
|
+
e.class.should eq(Ruote::Resque::InvalidJob)
|
86
|
+
end
|
87
|
+
|
88
|
+
Resque.enqueue_to(Ruote::Resque.configuration.reply_queue, 'AnotherJob')
|
89
|
+
|
90
|
+
# Ensure it is picked up by the receiver + cleanup afterwards
|
91
|
+
sleep 2
|
92
|
+
::Resque.reserve(Ruote::Resque.configuration.reply_queue)
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'raises InvalidWorkitem' do
|
97
|
+
|
98
|
+
Ruote::Resque::Receiver.any_instance.should_receive(:handle_error) do |e|
|
99
|
+
e.class.should eq(Ruote::Resque::InvalidWorkitem)
|
100
|
+
end
|
101
|
+
|
102
|
+
Resque.enqueue_to(Ruote::Resque.configuration.reply_queue, Ruote::Resque::ReplyJob, {})
|
103
|
+
|
104
|
+
# Ensure it is picked up by the receiver + cleanup afterwards
|
105
|
+
sleep 2
|
106
|
+
::Resque.reserve(Ruote::Resque.configuration.reply_queue)
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'participant/reply flow' do
|
113
|
+
|
114
|
+
context 'with no exceptions raised' do
|
115
|
+
|
116
|
+
before(:each) do
|
117
|
+
Ruote::Resque.register @board do
|
118
|
+
resque_bravo BravoJob, :rspec
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'completes successfully' do
|
123
|
+
|
124
|
+
wfid = @board.launch(definition)
|
125
|
+
|
126
|
+
r = @board.wait_for(wfid, :timeout => RUOTE_WAIT_TIMEOUT)
|
127
|
+
# wait until process terminates or hits an error
|
128
|
+
|
129
|
+
r['workitem'].should_not eq(nil)
|
130
|
+
r['workitem']['fields']['block_alpha'].should eq('was here')
|
131
|
+
r['workitem']['fields']['resque_bravo'].should eq('was here')
|
132
|
+
r['workitem']['fields']['block_charly'].should eq('was here')
|
133
|
+
r['workitem']['fields']['block_delta'].should eq(nil)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'with an exception raised' do
|
138
|
+
|
139
|
+
before(:each) do
|
140
|
+
Ruote::Resque.register @board do
|
141
|
+
resque_bravo BravoFailureJob, :rspec
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'routes to the error handler' do
|
146
|
+
|
147
|
+
wfid = @board.launch(definition)
|
148
|
+
|
149
|
+
r = @board.wait_for(wfid, :timeout => RUOTE_WAIT_TIMEOUT)
|
150
|
+
# wait until process terminates or hits an error
|
151
|
+
|
152
|
+
r['workitem'].should_not eq(nil)
|
153
|
+
r['workitem']['fields']['block_alpha'].should eq('was here')
|
154
|
+
r['workitem']['fields']['resque_bravo'].should eq(nil)
|
155
|
+
r['workitem']['fields']['block_charly'].should eq(nil)
|
156
|
+
r['workitem']['fields']['block_delta'].should eq('was here')
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'marks the job as failed in Resque' do
|
160
|
+
|
161
|
+
wfid = @board.launch(definition)
|
162
|
+
|
163
|
+
r = @board.wait_for(wfid, :timeout => RUOTE_WAIT_TIMEOUT)
|
164
|
+
# wait until process terminates or hits an error
|
165
|
+
|
166
|
+
Resque::Failure.count.should eq(1)
|
167
|
+
failed = Resque::Failure.all(0, 1)
|
168
|
+
failed.class.should eq(Hash)
|
169
|
+
failed['payload']['class'].should eq('BravoFailureJob')
|
170
|
+
failed['exception'].should eq('BravoError')
|
171
|
+
failed['error'].should eq('im a failure')
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'can be replayed from Ruote' do
|
175
|
+
|
176
|
+
definition = Ruote.define do
|
177
|
+
block_alpha
|
178
|
+
resque_bravo
|
179
|
+
block_charly
|
180
|
+
end
|
181
|
+
|
182
|
+
wfid = @board.launch(definition)
|
183
|
+
|
184
|
+
r = @board.wait_for(wfid, :timeout => RUOTE_WAIT_TIMEOUT)
|
185
|
+
error = @board.errors(wfid).first
|
186
|
+
|
187
|
+
expect(error.class).to eq(Ruote::ProcessError)
|
188
|
+
expect(error.klass).to eq('Ruote::ReceivedError')
|
189
|
+
expect(error.message).to eq('raised: Ruote::ReceivedError: BravoError: im a failure')
|
190
|
+
expect(error.trace).to include("/lib/resque/worker.rb:195:in `perform'")
|
191
|
+
|
192
|
+
Ruote::Resque.register @board do
|
193
|
+
resque_bravo BravoJob, :rspec
|
194
|
+
end
|
195
|
+
@board.replay_at_error(error)
|
196
|
+
|
197
|
+
r = @board.wait_for(wfid, :timeout => RUOTE_WAIT_TIMEOUT)
|
198
|
+
|
199
|
+
r['workitem'].should_not eq(nil)
|
200
|
+
r['workitem']['fields']['block_alpha'].should eq('was here')
|
201
|
+
r['workitem']['fields']['resque_bravo'].should eq('was here')
|
202
|
+
r['workitem']['fields']['block_charly'].should eq('was here')
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Ruote::Resque::ReplyJob do
|
6
|
+
|
7
|
+
context '::queue' do
|
8
|
+
|
9
|
+
let(:queue) { :my_queue }
|
10
|
+
let!(:default_queue) { Ruote::Resque.configuration.reply_queue }
|
11
|
+
|
12
|
+
before :each do
|
13
|
+
Ruote::Resque.configure do |config|
|
14
|
+
config.reply_queue = queue
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
after :each do
|
19
|
+
Ruote::Resque.configure do |config|
|
20
|
+
config.reply_queue = default_queue
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns the configured reply queue' do
|
25
|
+
expect(Ruote::Resque::ReplyJob.queue).to eq :my_queue
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
context '::perform' do
|
31
|
+
|
32
|
+
it 'returns nil' do
|
33
|
+
expect(Ruote::Resque::ReplyJob.perform).to be_nil
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Ruote::Resque do
|
6
|
+
|
7
|
+
context '::logger' do
|
8
|
+
|
9
|
+
it 'returns a Logger instance' do
|
10
|
+
expect(Ruote::Resque.logger.class).to eq Logger
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
context '::reply' do
|
16
|
+
|
17
|
+
it 'enqueues a reply' do
|
18
|
+
Ruote::Resque.reply({ 'field' => 'test' })
|
19
|
+
|
20
|
+
expected_job = { 'class' => 'Ruote::Resque::ReplyJob', 'args' => [{ 'field' => 'test' }] }
|
21
|
+
expect(::Resque.pop(:ruote_replies)).to eq expected_job
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context '::register' do
|
28
|
+
|
29
|
+
class MyAwesomeJob; end
|
30
|
+
|
31
|
+
let(:mock_dashboard) { Object.new }
|
32
|
+
|
33
|
+
it 'allows registration of participants with a block' do
|
34
|
+
|
35
|
+
mock_dashboard.should_receive(:register_participant).with('be_awesome', Ruote::Resque::Participant, { :class => MyAwesomeJob, :queue => :rspec })
|
36
|
+
Ruote::Resque.register mock_dashboard do
|
37
|
+
be_awesome MyAwesomeJob, :rspec
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
context '::configuration' do
|
45
|
+
|
46
|
+
it 'returns a Configuration object' do
|
47
|
+
expect(Ruote::Resque.configuration.class.to_s).to eq 'Ruote::Resque::Configuration'
|
48
|
+
end
|
49
|
+
|
50
|
+
context '::reply_queue' do
|
51
|
+
|
52
|
+
it 'is :ruote_replies by default' do
|
53
|
+
expect(Ruote::Resque.configuration.reply_queue).to eq :ruote_replies
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'is setable' do
|
57
|
+
Ruote::Resque.configuration.reply_queue = :another_queue
|
58
|
+
expect(Ruote::Resque.configuration.reply_queue).to eq :another_queue
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
context '::interval' do
|
64
|
+
|
65
|
+
it 'is 5 by default' do
|
66
|
+
expect(Ruote::Resque.configuration.interval).to eq 5
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'is setable' do
|
70
|
+
Ruote::Resque.configuration.interval = 1
|
71
|
+
expect(Ruote::Resque.configuration.interval).to eq 1
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
context '::logger' do
|
77
|
+
|
78
|
+
it 'is a Logger instance' do
|
79
|
+
expect(Ruote::Resque.configuration.logger.class).to eq Logger
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'is logging at INFO level' do
|
83
|
+
expect(Ruote::Resque.configuration.logger.level).to eq Logger::INFO
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'is a setable' do
|
87
|
+
|
88
|
+
class MyLogger < Logger
|
89
|
+
end
|
90
|
+
Ruote::Resque.configuration.logger = MyLogger.new(STDOUT)
|
91
|
+
|
92
|
+
expect(Ruote::Resque.configuration.logger.class).to eq MyLogger
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'coveralls'
|
4
|
+
Coveralls.wear!
|
5
|
+
|
6
|
+
require 'resque'
|
7
|
+
require 'ruote/resque'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
|
11
|
+
config.mock_with :rspec
|
12
|
+
|
13
|
+
config.before(:suite) do
|
14
|
+
Resque.redis.namespace = 'resque:rspec'
|
15
|
+
end
|
16
|
+
|
17
|
+
config.after(:each) do
|
18
|
+
Resque.queues.each { |queue| Resque.remove_queue(queue) }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruote-resque
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adrien Kohlbecker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: resque
|
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: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mutant
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
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: rubocop
|
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: yard
|
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: redcarpet
|
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: Resque participant/receiver pair for Ruote
|
98
|
+
email:
|
99
|
+
- adrien.kohlbecker@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- .rspec
|
106
|
+
- .rubocop.yml
|
107
|
+
- .travis.yml
|
108
|
+
- .yardopts
|
109
|
+
- Gemfile
|
110
|
+
- LICENSE
|
111
|
+
- README.md
|
112
|
+
- Rakefile
|
113
|
+
- lib/ruote/resque.rb
|
114
|
+
- lib/ruote/resque/client.rb
|
115
|
+
- lib/ruote/resque/job.rb
|
116
|
+
- lib/ruote/resque/participant.rb
|
117
|
+
- lib/ruote/resque/participant_registrar.rb
|
118
|
+
- lib/ruote/resque/receiver.rb
|
119
|
+
- lib/ruote/resque/reply_job.rb
|
120
|
+
- lib/ruote/resque/version.rb
|
121
|
+
- ruote-resque.gemspec
|
122
|
+
- spec/lib/ruote/resque/job_spec.rb
|
123
|
+
- spec/lib/ruote/resque/participant_registrar_spec.rb
|
124
|
+
- spec/lib/ruote/resque/participant_spec.rb
|
125
|
+
- spec/lib/ruote/resque/receiver_spec.rb
|
126
|
+
- spec/lib/ruote/resque/reply_job_spec.rb
|
127
|
+
- spec/lib/ruote/resque_spec.rb
|
128
|
+
- spec/spec_helper.rb
|
129
|
+
homepage: https://github.com/adrienkohlbecker/ruote-resque
|
130
|
+
licenses:
|
131
|
+
- MIT
|
132
|
+
metadata: {}
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ! '>='
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
requirements: []
|
148
|
+
rubyforge_project:
|
149
|
+
rubygems_version: 2.0.3
|
150
|
+
signing_key:
|
151
|
+
specification_version: 4
|
152
|
+
summary: Resque participant/receiver pair for Ruote
|
153
|
+
test_files:
|
154
|
+
- spec/lib/ruote/resque/job_spec.rb
|
155
|
+
- spec/lib/ruote/resque/participant_registrar_spec.rb
|
156
|
+
- spec/lib/ruote/resque/participant_spec.rb
|
157
|
+
- spec/lib/ruote/resque/receiver_spec.rb
|
158
|
+
- spec/lib/ruote/resque/reply_job_spec.rb
|
159
|
+
- spec/lib/ruote/resque_spec.rb
|
160
|
+
- spec/spec_helper.rb
|
161
|
+
has_rdoc:
|