rocketjob 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 201f922d37f2533e56109c053e3c9f4bdef30108
4
- data.tar.gz: 00598e8fb3039d7ce629f629bf270d3cc06f2e87
3
+ metadata.gz: 538bb39f185138c3fc5c4e007ef33470da129cfd
4
+ data.tar.gz: d0edc858039527f0fb33707bd563c3dacce0a968
5
5
  SHA512:
6
- metadata.gz: 447f63cf687c4e7ac10189aff59730874ea25f3e57b7d6ca301fd4a34178e91d68d4428f5be095c2a6a4a5426d603262c3d82c78e9f3988a9ed35d76bf75bddf
7
- data.tar.gz: e6a54a1b72cb2c7192894140eea4e49e42dc29049a85a77338de773e539c23a5e1ed09a5691a0a5c238a881e99398cb07b99eddf5abdda8e044971cf433766f3
6
+ metadata.gz: 73581f734996afa564614e434015ed6400461e755607b8af6652a29eee120d318415110058b9014e6224b15b594933a12d79ee023ec8feaf8c59b6335f173392
7
+ data.tar.gz: 896bc0dbfece93ff239aa56475a733bf1b310792e47d89be8bc17dce4c00e04ab426ba3532ad7cc1a864dc0f4a0eb280f8a177899a0ff1ec77f33d160adb0b3d
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # rocketjob [![Gem Version](https://badge.fury.io/rb/rocketjob.svg)](http://badge.fury.io/rb/rocketjob) [![Build Status](https://secure.travis-ci.org/rocketjob/rocketjob.png?branch=master)](http://travis-ci.org/rocketjob/rocketjob) ![](http://ruby-gem-downloads-badge.herokuapp.com/rocketjob?type=total)
2
2
 
3
- High volume, priority based, distributed, background job processing solution for Ruby.
3
+ Enterprise Batch Processing System focused on performance, scalability, reliability, and visibility of every job in the system.
4
+
5
+ Outgrown existing solutions? Or, start small and scale up later.
6
+
7
+ Works with or without Rails.
4
8
 
5
9
  ## Status
6
10
 
@@ -22,19 +22,7 @@ module RocketJob
22
22
 
23
23
  # Create a job and process it immediately in-line by this thread
24
24
  def now(method, *args, &block)
25
- job = build(method, *args, &block)
26
- # Call validations
27
- if job.respond_to?(:validate!)
28
- job.validate!
29
- elsif job.invalid?
30
- raise(MongoMapper::DocumentNotValid, "Validation failed: #{job.errors.messages.join(', ')}")
31
- end
32
- worker = RocketJob::Worker.new(name: 'inline')
33
- worker.started
34
- job.start
35
- while job.running? && !job.work(worker)
36
- end
37
- job
25
+ build(method, *args, &block).work_now
38
26
  end
39
27
 
40
28
  # Build a Rocket Job instance
@@ -134,6 +122,8 @@ module RocketJob
134
122
  begin
135
123
  # before_perform
136
124
  call_method(perform_method, arguments, event: :before, log_level: log_level)
125
+ # Allow before perform to explicitly fail this job
126
+ return unless running?
137
127
 
138
128
  # perform
139
129
  ret = call_method(perform_method, arguments, log_level: log_level)
@@ -141,18 +131,39 @@ module RocketJob
141
131
  self.result = (ret.is_a?(Hash) || ret.is_a?(BSON::OrderedHash)) ? ret : {result: ret}
142
132
  end
143
133
 
134
+ # Only run after perform if perform did not explicitly fail the job
135
+ return unless running?
136
+
144
137
  # after_perform
145
138
  call_method(perform_method, arguments, event: :after, log_level: log_level)
146
139
 
147
- complete!
140
+ new_record? ? complete : complete!
148
141
  rescue StandardError => exc
149
- fail!(worker.name, exc) unless failed?
142
+ fail(worker.name, exc) if may_fail?
150
143
  logger.error("Exception running #{self.class.name}##{perform_method}", exc)
144
+ save! unless new_record?
151
145
  raise exc if RocketJob::Config.inline_mode
152
146
  end
153
147
  false
154
148
  end
155
149
 
150
+ # Validates and runs the work on this job now in the current thread
151
+ # Returns this job once it has finished running
152
+ def work_now
153
+ # Call validations
154
+ if respond_to?(:validate!)
155
+ validate!
156
+ elsif invalid?
157
+ raise(MongoMapper::DocumentNotValid, "Validation failed: #{errors.messages.join(', ')}")
158
+ end
159
+ worker = RocketJob::Worker.new(name: 'inline')
160
+ worker.started
161
+ start if may_start?
162
+ while running? && !work(worker)
163
+ end
164
+ self
165
+ end
166
+
156
167
  protected
157
168
 
158
169
  # Calls a method on this job, if it is defined
@@ -1,18 +1,16 @@
1
1
  # encoding: UTF-8
2
- require 'sync_attr'
3
2
  module RocketJob
4
3
  # Centralized Configuration for Rocket Jobs
5
4
  class Config
6
5
  include MongoMapper::Document
7
- include SyncAttr
8
6
 
9
7
  # Prevent data in MongoDB from re-defining the model behavior
10
8
  #self.static_keys = true
11
9
 
12
10
  # Returns the single instance of the Rocket Job Configuration for this site
13
11
  # in a thread-safe way
14
- sync_cattr_reader(:instance) do
15
- begin
12
+ def self.instance
13
+ @@instance ||= begin
16
14
  first || create
17
15
  rescue StandardError
18
16
  # In case another process has already created the first document
@@ -22,7 +20,7 @@ module RocketJob
22
20
 
23
21
  # By enabling inline_mode jobs will be called in-line
24
22
  # No worker processes will be created, nor threads created
25
- sync_cattr_accessor(:inline_mode) { false }
23
+ cattr_accessor(:inline_mode) { false }
26
24
 
27
25
  # @formatter:off
28
26
  # The maximum number of worker threads to create on any one worker
@@ -280,13 +280,16 @@ module RocketJob
280
280
 
281
281
  # Queues the job for the supplied pathname
282
282
  def later(pathname)
283
- job_class.perform_later(*arguments) do |job|
284
- job.perform_method = perform_method
285
- # Set properties
286
- properties.each_pair { |k, v| job.send("#{k}=".to_sym, v) }
287
-
288
- upload_file(job, pathname)
289
- end
283
+ job = job_class.new(
284
+ properties.merge(
285
+ arguments: arguments,
286
+ properties: properties,
287
+ perform_method: perform_method
288
+ )
289
+ )
290
+ upload_file(job, pathname)
291
+ job.save!
292
+ job
290
293
  end
291
294
 
292
295
  protected
@@ -315,9 +318,14 @@ module RocketJob
315
318
 
316
319
  # Archives the file for a job where there was no #file_store_upload or #upload method
317
320
  def upload_default(job, pathname)
318
- # The first argument must be a hash
319
- job.arguments << {} if job.arguments.size == 0
320
- job.arguments.first[:full_file_name] = archive_file(job, pathname)
321
+ full_file_name = archive_file(job, pathname)
322
+ if job.respond_to?(:full_file_name=)
323
+ job.full_file_name = full_file_name
324
+ elsif job.arguments.first.is_a?(Hash)
325
+ job.arguments.first[:full_file_name] = full_file_name
326
+ else
327
+ raise(ArgumentError, "#{job_class_name} must either have attribute 'full_file_name' or the first argument must be a Hash")
328
+ end
321
329
  end
322
330
 
323
331
  # Move the file to the archive directory
@@ -120,6 +120,20 @@ module RocketJob
120
120
 
121
121
  validates_presence_of :state, :failure_count, :created_at, :perform_method
122
122
  validates :priority, inclusion: 1..100
123
+ validates :log_level, inclusion: SemanticLogger::LEVELS + [nil]
124
+
125
+ # User definable properties in Dirmon Entry
126
+ def self.rocket_job_properties
127
+ @rocket_job_properties ||= (self == RocketJob::Job ? [] : superclass.rocket_job_properties)
128
+ end
129
+
130
+ # Add to user definable properties in Dirmon Entry
131
+ def self.public_rocket_job_properties(*properties)
132
+ rocket_job_properties.concat(properties).uniq!
133
+ end
134
+
135
+ # User definable properties in Dirmon Entry
136
+ public_rocket_job_properties :description, :priority, :perform_method, :log_level, :arguments
123
137
 
124
138
  # State Machine events and transitions
125
139
  #
@@ -41,27 +41,29 @@ module RocketJob
41
41
 
42
42
  # Number of seconds between directory scans. Default 5 mins
43
43
  key :check_seconds, Float, default: 300.0
44
+ key :previous_file_names, Hash # Hash[file_name, size]
44
45
 
45
46
  # Iterate over each Dirmon entry looking for new files
46
47
  # If a new file is found, it is not processed immediately, instead
47
48
  # it is passed to the next run of this job along with the file size.
48
49
  # If the file size has not changed, the Job is kicked off.
49
- def perform(previous_file_names={})
50
- new_file_names = check_directories(previous_file_names)
50
+ def perform
51
+ check_directories
51
52
  ensure
52
53
  # Run again in the future, even if this run fails with an exception
53
- self.class.perform_later(new_file_names || previous_file_names) do |job|
54
- job.priority = priority
55
- job.check_seconds = check_seconds
56
- job.run_at = Time.now + check_seconds
57
- end
54
+ self.class.create!(
55
+ previous_file_names: previous_file_names,
56
+ priority: priority,
57
+ check_seconds: check_seconds,
58
+ run_at: Time.now + check_seconds
59
+ )
58
60
  end
59
61
 
60
62
  protected
61
63
 
62
64
  # Checks the directories for new files, starting jobs if files have not changed
63
65
  # since the last run
64
- def check_directories(previous_file_names)
66
+ def check_directories
65
67
  new_file_names = {}
66
68
  DirmonEntry.where(state: :enabled).each do |entry|
67
69
  entry.each do |pathname|
@@ -73,7 +75,7 @@ module RocketJob
73
75
  end
74
76
  end
75
77
  end
76
- new_file_names
78
+ self.previous_file_names = new_file_names
77
79
  end
78
80
 
79
81
  # Checks if a file should result in starting a job
@@ -1,4 +1,4 @@
1
1
  # encoding: UTF-8
2
2
  module RocketJob #:nodoc
3
- VERSION = '1.2.1'
3
+ VERSION = '1.3.0'
4
4
  end
@@ -1,6 +1,5 @@
1
1
  # encoding: UTF-8
2
2
  require 'socket'
3
- require 'sync_attr'
4
3
  require 'aasm'
5
4
  module RocketJob
6
5
  # Worker
@@ -30,7 +29,6 @@ module RocketJob
30
29
  class Worker
31
30
  include MongoMapper::Document
32
31
  include AASM
33
- include SyncAttr
34
32
  include SemanticLogger::Loggable
35
33
 
36
34
  # Prevent data in MongoDB from re-defining the model behavior
@@ -164,7 +162,7 @@ module RocketJob
164
162
  @thread_pool ||= []
165
163
  end
166
164
 
167
- # Run this instance of the worker
165
+ # Management Thread
168
166
  def run
169
167
  Thread.current.name = 'rocketjob main'
170
168
  build_heartbeat unless heartbeat
@@ -182,7 +180,7 @@ module RocketJob
182
180
  'heartbeat.current_threads' => thread_pool_count
183
181
  )
184
182
 
185
- # Reload the worker model every 10 heartbeats in case its config was changed
183
+ # Reload the worker model every few heartbeats in case its config was changed
186
184
  # TODO make 3 configurable
187
185
  if count >= 3
188
186
  reload
@@ -316,11 +314,13 @@ module RocketJob
316
314
  RocketJob::Job.requeue_dead_worker(name)
317
315
  end
318
316
 
319
- # Mutex protected shutdown indicator
320
- sync_cattr_accessor :shutdown do
321
- false
317
+ # Shutdown indicator
318
+ def self.shutdown
319
+ @@shutdown
322
320
  end
323
321
 
322
+ @@shutdown = false
323
+
324
324
  # Register handlers for the various signals
325
325
  # Term:
326
326
  # Perform clean shutdown
@@ -3,6 +3,16 @@ require_relative 'jobs/test_job'
3
3
 
4
4
  # Unit Test for RocketJob::Job
5
5
  class DirmonEntryTest < Minitest::Test
6
+ class WithFullFileNameJob < RocketJob::Job
7
+ # Dirmon will store the filename in this property when starting the job
8
+ key :full_file_name, String
9
+
10
+ def perform
11
+ # Do something with the file name stored in :full_file_name
12
+ end
13
+ end
14
+
15
+
6
16
  describe RocketJob::DirmonEntry do
7
17
  describe '.config' do
8
18
  it 'support multiple databases' do
@@ -186,11 +196,17 @@ class DirmonEntryTest < Minitest::Test
186
196
  @entry = RocketJob::DirmonEntry.new(
187
197
  pattern: 'test/files/**/*',
188
198
  job_class_name: 'Jobs::TestJob',
189
- arguments: [{input: 'yes'}],
199
+ arguments: [{}],
190
200
  properties: {priority: 23, perform_method: :event},
191
201
  archive_directory: @archive_directory
192
202
  )
193
- @job = Jobs::TestJob.new
203
+ @job = Jobs::TestJob.new(
204
+ @entry.properties.merge(
205
+ arguments: @entry.arguments,
206
+ properties: @entry.properties,
207
+ perform_method: @entry.perform_method
208
+ )
209
+ )
194
210
  @file = Tempfile.new('archive')
195
211
  @file_name = @file.path
196
212
  @pathname = Pathname.new(@file_name)
@@ -222,10 +238,30 @@ class DirmonEntryTest < Minitest::Test
222
238
  end
223
239
 
224
240
  describe '#upload_default' do
225
- it 'upload' do
241
+ it 'sets full_file_name in Hash argument' do
226
242
  @entry.send(:upload_default, @job, @pathname)
227
243
  assert_equal @archive_real_name, @job.arguments.first[:full_file_name], @job.arguments
228
244
  end
245
+
246
+ it 'sets full_file_name property' do
247
+ @entry = RocketJob::DirmonEntry.new(
248
+ pattern: 'test/files/**/*',
249
+ job_class_name: 'DirmonEntryTest::WithFullFileNameJob',
250
+ archive_directory: @archive_directory
251
+ )
252
+ assert @entry.valid?, @entry.errors.messages
253
+ job = @entry.job_class.new
254
+ @entry.send(:upload_default, job, @pathname)
255
+ archive_real_name = @archive_path.join("#{job.id}_#{File.basename(@file_name)}").to_s
256
+ assert_equal archive_real_name, job.full_file_name, job.arguments
257
+ end
258
+
259
+ it 'handles non hash argument and missing property' do
260
+ @job.arguments = [1]
261
+ assert_raises ArgumentError do
262
+ @entry.send(:upload_default, @job, @pathname)
263
+ end
264
+ end
229
265
  end
230
266
 
231
267
  describe '#upload_file' do
@@ -73,8 +73,7 @@ class DirmonJobTest < Minitest::Test
73
73
  end
74
74
 
75
75
  it 'no files' do
76
- previous_file_names = {}
77
- result = @dirmon_job.send(:check_directories, previous_file_names)
76
+ result = @dirmon_job.send(:check_directories)
78
77
  assert_equal 0, result.count
79
78
  end
80
79
 
@@ -82,37 +81,40 @@ class DirmonJobTest < Minitest::Test
82
81
  create_file("#{@directory}/abc/file1", 5)
83
82
  create_file("#{@directory}/abc/file2", 10)
84
83
 
85
- previous_file_names = {}
86
- result = @dirmon_job.send(:check_directories, previous_file_names)
87
- assert_equal 2, result.count, result.inspect
88
- assert_equal 5, result.values.first, result.inspect
89
- assert_equal 10, result.values.second, result.inspect
84
+ result = @dirmon_job.send(:check_directories)
85
+ assert_equal 2, result.count, result
86
+ assert_equal 5, result.values.first, result
87
+ assert_equal 10, result.values.second, result
90
88
  end
91
89
 
92
90
  it 'allow files to grow' do
93
91
  create_file("#{@directory}/abc/file1", 5)
94
92
  create_file("#{@directory}/abc/file2", 10)
95
- previous_file_names = {}
96
- @dirmon_job.send(:check_directories, previous_file_names)
93
+ @dirmon_job.send(:check_directories)
97
94
  create_file("#{@directory}/abc/file1", 10)
98
95
  create_file("#{@directory}/abc/file2", 15)
99
- result = @dirmon_job.send(:check_directories, previous_file_names)
100
- assert_equal 2, result.count, result.inspect
101
- assert_equal 10, result.values.first, result.inspect
102
- assert_equal 15, result.values.second, result.inspect
96
+ result = @dirmon_job.send(:check_directories)
97
+ assert_equal 2, result.count, result
98
+ assert_equal 10, result.values.first, result
99
+ assert_equal 15, result.values.second, result
103
100
  end
104
101
 
105
102
  it 'start all files' do
106
103
  create_file("#{@directory}/abc/file1", 5)
107
104
  create_file("#{@directory}/abc/file2", 10)
108
- previous_file_names = @dirmon_job.send(:check_directories, {})
105
+ files = @dirmon_job.send(:check_directories)
106
+ assert_equal 2, files.count, files
107
+ assert_equal 2, @dirmon_job.previous_file_names.count, files
108
+
109
+ # files = @dirmon_job.send(:check_directories)
110
+ # assert_equal 0, files.count, files
109
111
 
110
112
  count = 0
111
113
  result = RocketJob::DirmonEntry.stub_any_instance(:later, -> path { count += 1 }) do
112
- @dirmon_job.send(:check_directories, previous_file_names)
114
+ @dirmon_job.send(:check_directories)
113
115
  end
116
+ assert_equal 0, result.count, result
114
117
  assert 2, count
115
- assert_equal 0, result.count, result.inspect
116
118
  end
117
119
 
118
120
  it 'skip files in archive directory' do
@@ -125,11 +127,11 @@ class DirmonJobTest < Minitest::Test
125
127
  FileUtils.makedirs(@entry.archive_pathname(file_pathname))
126
128
  create_file("#{@entry.archive_pathname(file_pathname)}/file3", 10)
127
129
 
128
- result = @dirmon_job.send(:check_directories, {})
130
+ result = @dirmon_job.send(:check_directories)
129
131
 
130
- assert_equal 2, result.count, result.inspect
131
- assert_equal 5, result.values.first, result.inspect
132
- assert_equal 10, result.values.second, result.inspect
132
+ assert_equal 2, result.count, result
133
+ assert_equal 5, result.values.first, result
134
+ assert_equal 10, result.values.second, result
133
135
  end
134
136
  end
135
137
 
@@ -147,10 +149,12 @@ class DirmonJobTest < Minitest::Test
147
149
  RocketJob::Jobs::DirmonJob.destroy_all
148
150
  RocketJob::Jobs::DirmonJob.stub_any_instance(:check_directories, new_file_names) do
149
151
  # perform_now does not save the job, just runs it
150
- dirmon_job = RocketJob::Jobs::DirmonJob.perform_now(previous_file_names) do |job|
151
- job.priority = 11
152
- job.check_seconds = 30
153
- end
152
+ dirmon_job = RocketJob::Jobs::DirmonJob.new(
153
+ previous_file_names: previous_file_names,
154
+ priority: 11,
155
+ check_seconds: 30
156
+ )
157
+ dirmon_job.work_now
154
158
  end
155
159
  assert dirmon_job.completed?, dirmon_job.status.inspect
156
160
 
@@ -171,14 +175,15 @@ class DirmonJobTest < Minitest::Test
171
175
  RocketJob::Jobs::DirmonJob.destroy_all
172
176
  RocketJob::Jobs::DirmonJob.stub_any_instance(:check_directories, -> previous { raise RuntimeError.new("Oh no") }) do
173
177
  # perform_now does not save the job, just runs it
174
- dirmon_job = RocketJob::Jobs::DirmonJob.perform_now do |job|
175
- job.priority = 11
176
- job.check_seconds = 30
177
- end
178
+ dirmon_job = RocketJob::Jobs::DirmonJob.create!(
179
+ priority: 11,
180
+ check_seconds: 30
181
+ )
182
+ dirmon_job.work_now
178
183
  end
179
184
  assert dirmon_job.failed?, dirmon_job.status.inspect
180
185
 
181
- # It it have enqueued another instance to run in the future
186
+ # Must have enqueued another instance to run in the future
182
187
  assert_equal 2, RocketJob::Jobs::DirmonJob.count
183
188
  assert new_dirmon_job = RocketJob::Jobs::DirmonJob.last
184
189
  assert new_dirmon_job.run_at
data/test/job_test.rb CHANGED
@@ -154,7 +154,8 @@ class JobTest < Minitest::Test
154
154
  @job.destroy_on_complete = true
155
155
  @job.start!
156
156
  assert_equal false, @job.work(@worker)
157
- assert_equal nil, RocketJob::Job.find_by_id(@job.id)
157
+ assert @job.completed?, @job.state
158
+ assert_equal 0, RocketJob::Job.where(id: @job.id).count
158
159
  end
159
160
 
160
161
  it 'silence logging when log_level is set' do
@@ -254,8 +255,9 @@ class JobTest < Minitest::Test
254
255
  it 'requeue jobs from dead workers' do
255
256
  worker_name = 'server:12345'
256
257
  @job.worker_name = worker_name
258
+ assert @job.valid?, @job.errors.messages
257
259
  @job.start!
258
- assert @job.running?
260
+ assert @job.running?, @job.state
259
261
 
260
262
  @job.requeue
261
263
  assert @job.queued?
@@ -273,7 +275,7 @@ class JobTest < Minitest::Test
273
275
  worker_name = 'server:12345'
274
276
  @job.worker_name = worker_name
275
277
  @job.start!
276
- assert @job.running?
278
+ assert @job.running?, @job.state
277
279
 
278
280
  worker_name2 = 'server:76467'
279
281
  @job2.worker_name = worker_name2
data/test/test_helper.rb CHANGED
@@ -8,7 +8,13 @@ require 'rocketjob'
8
8
  require 'awesome_print'
9
9
  require 'symmetric-encryption'
10
10
 
11
- Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
11
+ if ENV['DETAILED_TESTS'].present?
12
+ # See every test and how long it took
13
+ MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
14
+ else
15
+ # Only show failed tests
16
+ MiniTest::Reporters.use! MiniTest::Reporters::ProgressReporter.new
17
+ end
12
18
 
13
19
  SemanticLogger.add_appender('test.log', &SemanticLogger::Appender::Base.colorized_formatter)
14
20
  SemanticLogger.default_level = :debug
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rocketjob
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-15 00:00:00.000000000 Z
11
+ date: 2015-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aasm
@@ -94,22 +94,8 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '3.0'
97
- - !ruby/object:Gem::Dependency
98
- name: sync_attr
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '2.0'
104
- type: :runtime
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '2.0'
111
- description: Next generation, high performance, priority based, distributed, background
112
- job processing solution
97
+ description: Enterprise Batch Processing System focused on performance, scalability,
98
+ reliability, and visibility of every job in the system.
113
99
  email:
114
100
  - reidmo@gmail.com
115
101
  executables:
@@ -165,7 +151,7 @@ rubyforge_project:
165
151
  rubygems_version: 2.4.5.1
166
152
  signing_key:
167
153
  specification_version: 4
168
- summary: Next generation background job processing system for Ruby, JRuby and Rubinius
154
+ summary: Enterprise Batch Processing System for Ruby, JRuby, and Rubinius
169
155
  test_files:
170
156
  - test/config/mongo.yml
171
157
  - test/dirmon_entry_test.rb