einhorn 0.4.7 → 0.4.8
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/einhorn +5 -0
- data/einhorn.gemspec +2 -2
- data/example/plugin.rb +40 -0
- data/lib/einhorn.rb +15 -0
- data/lib/einhorn/third.rb +1 -0
- data/lib/einhorn/third/little-plugger.rb +2 -0
- data/lib/einhorn/third/little-plugger/.gitignore +17 -0
- data/lib/einhorn/third/little-plugger/History.txt +30 -0
- data/lib/einhorn/third/little-plugger/README.rdoc +53 -0
- data/lib/einhorn/third/little-plugger/Rakefile +31 -0
- data/lib/einhorn/third/little-plugger/lib/little-plugger.rb +325 -0
- data/lib/einhorn/third/little-plugger/spec/little-plugger_spec.rb +26 -0
- data/lib/einhorn/third/little-plugger/spec/spec_helper.rb +4 -0
- data/lib/einhorn/version.rb +1 -1
- data/test/_lib.rb +12 -0
- data/test/unit/einhorn.rb +10 -10
- data/test/unit/einhorn/client.rb +12 -12
- data/test/unit/einhorn/command.rb +5 -5
- data/test/unit/einhorn/command/interface.rb +8 -8
- data/test/unit/einhorn/event.rb +8 -8
- data/test/unit/einhorn/worker_pool.rb +8 -8
- metadata +23 -14
- data/test/test_helper.rb +0 -8
data/bin/einhorn
CHANGED
@@ -187,6 +187,7 @@ if true # $0 == __FILE__
|
|
187
187
|
Einhorn::TransientState.script_name = $0
|
188
188
|
Einhorn::TransientState.argv = ARGV.dup
|
189
189
|
Einhorn::TransientState.environ = ENV.to_hash
|
190
|
+
Einhorn.initialize_plugins
|
190
191
|
|
191
192
|
optparse = OptionParser.new do |opts|
|
192
193
|
opts.on('-b ADDR', '--bind ADDR', 'Bind an address and add the corresponding FD via the environment') do |addr|
|
@@ -274,6 +275,8 @@ if true # $0 == __FILE__
|
|
274
275
|
Einhorn::Command.louder(false)
|
275
276
|
end
|
276
277
|
|
278
|
+
Einhorn.plugins_send(:optparse, opts)
|
279
|
+
|
277
280
|
opts.on('--nice MASTER[:WORKER=0][:RENICE_CMD=/usr/bin/renice]', 'Unix nice level at which to run the einhorn processes. If not running as root, make sure to ulimit -e as appopriate.') do |nice|
|
278
281
|
master, worker, renice_cmd = nice.split(':')
|
279
282
|
master = Integer(master) if master
|
@@ -306,6 +309,8 @@ if true # $0 == __FILE__
|
|
306
309
|
exit(1)
|
307
310
|
end
|
308
311
|
|
312
|
+
Einhorn.plugins_send(:post_optparse)
|
313
|
+
|
309
314
|
ret = Einhorn.run
|
310
315
|
begin
|
311
316
|
exit(ret)
|
data/einhorn.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
|
17
17
|
gem.add_development_dependency('rake')
|
18
|
-
gem.add_development_dependency('
|
19
|
-
gem.add_development_dependency('mocha')
|
18
|
+
gem.add_development_dependency('minitest', '< 5.0')
|
19
|
+
gem.add_development_dependency('mocha', '~> 0.13')
|
20
20
|
gem.version = Einhorn::VERSION
|
21
21
|
end
|
data/example/plugin.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#
|
2
|
+
# plugin.rb - An example Einhorn plugin.
|
3
|
+
#
|
4
|
+
# Including this file in [yourgemhere]/lib/einhorn/plugins/ will cause Einhorn
|
5
|
+
# to load it. This example plugin defines all the methods that Einhorn
|
6
|
+
# recognizes and will invoke, although none of these methods is required.
|
7
|
+
#
|
8
|
+
|
9
|
+
module Einhorn::Plugins
|
10
|
+
module ExamplePlugin
|
11
|
+
def self.initialize_example_plugin
|
12
|
+
# The initializer method must be named `initialize_##[plugin_name]',
|
13
|
+
# where [plugin_name] is the name of the plugin module or class in
|
14
|
+
# lower_case_with_underscores.
|
15
|
+
puts 'I will be called before einhorn does any work.'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.optparse(opts)
|
19
|
+
@options = {}
|
20
|
+
opts.on("--my-option X", "Patch einhorn with additional options!") do |x|
|
21
|
+
@options[:yay] = x
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.post_optparse
|
26
|
+
# Called after all options native to einhorn or patched by any plugins
|
27
|
+
# are parsed.
|
28
|
+
@required_x = @options.fetch(:yay)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.event_loop
|
32
|
+
# Called each time einhorn enters its event loop, in which it cleans up
|
33
|
+
# any terminated children and respawns them.
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.exit
|
37
|
+
# Called after the event loop terminates, just before einhorn exits.
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/einhorn.rb
CHANGED
@@ -6,7 +6,18 @@ require 'socket'
|
|
6
6
|
require 'tmpdir'
|
7
7
|
require 'yaml'
|
8
8
|
|
9
|
+
require 'einhorn/third/little-plugger'
|
10
|
+
|
9
11
|
module Einhorn
|
12
|
+
extend Third::LittlePlugger
|
13
|
+
module Plugins; end
|
14
|
+
|
15
|
+
def self.plugins_send(sym, *args)
|
16
|
+
plugins.values.each do |plugin|
|
17
|
+
plugin.send(sym, *args) if plugin.respond_to? sym
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
10
21
|
module AbstractState
|
11
22
|
def default_state; raise NotImplementedError.new('Override in extended modules'); end
|
12
23
|
def state; @state ||= default_state; end
|
@@ -321,6 +332,8 @@ module Einhorn
|
|
321
332
|
|
322
333
|
while Einhorn::State.respawn || Einhorn::State.children.size > 0
|
323
334
|
log_debug("Entering event loop")
|
335
|
+
Einhorn.plugins_send(:event_loop)
|
336
|
+
|
324
337
|
# All of these are non-blocking
|
325
338
|
Einhorn::Command.reap
|
326
339
|
Einhorn::Command.replenish
|
@@ -329,6 +342,8 @@ module Einhorn
|
|
329
342
|
# Make sure to do this last, as it's blocking.
|
330
343
|
Einhorn::Event.loop_once
|
331
344
|
end
|
345
|
+
|
346
|
+
Einhorn.plugins_send(:exit)
|
332
347
|
end
|
333
348
|
end
|
334
349
|
|
@@ -0,0 +1 @@
|
|
1
|
+
module Einhorn; module Third; end; end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# The list of files that should be ignored by Mr Bones.
|
2
|
+
# Lines that start with '#' are comments.
|
3
|
+
#
|
4
|
+
# A .gitignore file can be used instead by setting it as the ignore
|
5
|
+
# file in your Rakefile:
|
6
|
+
#
|
7
|
+
# PROJ.ignore_file = '.gitignore'
|
8
|
+
#
|
9
|
+
# For a project with a C extension, the following would be a good set of
|
10
|
+
# exclude patterns (uncomment them if you want to use them):
|
11
|
+
# *.[oa]
|
12
|
+
# *~
|
13
|
+
announcement.txt
|
14
|
+
coverage
|
15
|
+
doc
|
16
|
+
pkg
|
17
|
+
.rvmrc
|
@@ -0,0 +1,30 @@
|
|
1
|
+
== 1.1.3 / 2011-11-17
|
2
|
+
|
3
|
+
* 1 bug fix
|
4
|
+
* Ensuring gem files are in a sorted order
|
5
|
+
|
6
|
+
== 1.1.2 / 2010-02-01
|
7
|
+
|
8
|
+
* 1 bug fix
|
9
|
+
* Resovling some circular dependencies
|
10
|
+
|
11
|
+
== 1.1.1 / 2009-11-08
|
12
|
+
|
13
|
+
* 1 bug fix
|
14
|
+
* Catching script errors and standard errors when loading plugins
|
15
|
+
|
16
|
+
== 1.1.0 / 2009-11-04
|
17
|
+
|
18
|
+
* 2 minor enhancements
|
19
|
+
* Loading the first plugin found by name (instead of the last)
|
20
|
+
* Plugins can be disregarded so they will not be loaded
|
21
|
+
|
22
|
+
== 1.0.1 / 2009-08-14
|
23
|
+
|
24
|
+
* 1 bug fix
|
25
|
+
* Using the wrong file extension for the File#basename call
|
26
|
+
|
27
|
+
== 1.0.0 / 2009-07-16
|
28
|
+
|
29
|
+
* 1 major enhancement
|
30
|
+
* Birthday!
|
@@ -0,0 +1,53 @@
|
|
1
|
+
= Little Plugger
|
2
|
+
* by Tim Pease
|
3
|
+
* http://github.com/TwP/little-plugger/tree/master
|
4
|
+
|
5
|
+
=== DESCRIPTION:
|
6
|
+
|
7
|
+
LittlePlugger is a module that provides Gem based plugin management.
|
8
|
+
By extending your own class or module with LittlePlugger you can easily
|
9
|
+
manage the loading and initializing of plugins provided by other gems.
|
10
|
+
|
11
|
+
=== FEATURES:
|
12
|
+
|
13
|
+
* List of plugins so that some plugins can be excluded while others are
|
14
|
+
loaded by default.
|
15
|
+
* Loading and initializing of plugins.
|
16
|
+
* Access to the plugin classes and modules.
|
17
|
+
|
18
|
+
LittlePlugger is a distallation of the plugin system from Hoe. It has been
|
19
|
+
"genericized" and encapsulated into its own easy to use module.
|
20
|
+
|
21
|
+
=== REQUIREMENTS:
|
22
|
+
|
23
|
+
Since Little Plugger is a Gem based plugin system, Ruby Gems must be
|
24
|
+
installed on your system.
|
25
|
+
|
26
|
+
=== INSTALL:
|
27
|
+
|
28
|
+
gem install little-plugger
|
29
|
+
|
30
|
+
=== LICENSE:
|
31
|
+
|
32
|
+
(The MIT License)
|
33
|
+
|
34
|
+
Copyright (c) 2009-2011
|
35
|
+
|
36
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
37
|
+
a copy of this software and associated documentation files (the
|
38
|
+
'Software'), to deal in the Software without restriction, including
|
39
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
40
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
41
|
+
permit persons to whom the Software is furnished to do so, subject to
|
42
|
+
the following conditions:
|
43
|
+
|
44
|
+
The above copyright notice and this permission notice shall be
|
45
|
+
included in all copies or substantial portions of the Software.
|
46
|
+
|
47
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
48
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
49
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
50
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
51
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
52
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
53
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'bones'
|
4
|
+
rescue LoadError
|
5
|
+
abort '### please install the "bones" gem ###'
|
6
|
+
end
|
7
|
+
|
8
|
+
ensure_in_path 'lib'
|
9
|
+
require 'little-plugger'
|
10
|
+
|
11
|
+
task :default => 'spec:run'
|
12
|
+
task 'gem:release' => 'spec:run'
|
13
|
+
|
14
|
+
Bones {
|
15
|
+
name 'little-plugger'
|
16
|
+
authors 'Tim Pease'
|
17
|
+
email 'tim.pease@gmail.com'
|
18
|
+
url 'http://gemcutter.org/gems/little-plugger'
|
19
|
+
version LittlePlugger::VERSION
|
20
|
+
readme_file 'README.rdoc'
|
21
|
+
|
22
|
+
spec.opts.concat %w[--color --format documentation]
|
23
|
+
use_gmail
|
24
|
+
|
25
|
+
depend_on 'rspec', :development => true
|
26
|
+
}
|
27
|
+
|
28
|
+
# depending on bones (even as a development dependency) creates a circular
|
29
|
+
# reference that prevents the auto install of little-plugger when instsalling
|
30
|
+
# bones
|
31
|
+
::Bones.config.gem._spec.dependencies.delete_if {|d| d.name == 'bones'}
|
@@ -0,0 +1,325 @@
|
|
1
|
+
|
2
|
+
# == Synopsis
|
3
|
+
# LittlePlugger is a module that provides Gem based plugin management.
|
4
|
+
# By extending your own class or module with LittlePlugger you can easily
|
5
|
+
# manage the loading and initializing of plugins provided by other gems.
|
6
|
+
#
|
7
|
+
# == Details
|
8
|
+
# Plugins are great! They allow other developers to add functionality to
|
9
|
+
# an application but relieve the application developer of the responsibility
|
10
|
+
# for mainting some other developer's plugin code. LittlePlugger aims to
|
11
|
+
# make it dead simple to manage external plugins as gems.
|
12
|
+
#
|
13
|
+
# === Naming
|
14
|
+
# Every plugin managed by LittlePlugger will have a name represented as a
|
15
|
+
# Symbol. This name is used to register the plugin, load the plugin file,
|
16
|
+
# and manage the plugin class/module. Here are the three rules for plugin
|
17
|
+
# names:
|
18
|
+
#
|
19
|
+
# 1) all lowercase with underscores
|
20
|
+
# 2) maps to a file of the same name with an '.rb' extension
|
21
|
+
# 3) converting the name to camel case yields the plugin class / module
|
22
|
+
#
|
23
|
+
# These rules are essentially the standard ruby practice of naming files
|
24
|
+
# after the class / module the file defines.
|
25
|
+
#
|
26
|
+
# === Finding & Loading
|
27
|
+
# Plugins are found by searching through the lib folders of all installed
|
28
|
+
# gems; these gems are not necessarily loaded - just searched. If the lib
|
29
|
+
# folder has a subdirectory that matches the +plugin_path+, then all ruby
|
30
|
+
# files in the gem's +plugin_path+ are noted for later loading.
|
31
|
+
#
|
32
|
+
# A file is only loaded if the basename of the file matches one of the
|
33
|
+
# registered plugin names. If no plugins are registered, then every file in
|
34
|
+
# the +plugin_path+ is loaded.
|
35
|
+
#
|
36
|
+
# The plugin classes / modules are all expected to live in the same
|
37
|
+
# namespace for a particular application. For example, all plugins for the
|
38
|
+
# "Foo" application should reside in a "Foo::Plugins" namespace. This allows
|
39
|
+
# the plugins to be automatically initialized by LittlePlugger.
|
40
|
+
#
|
41
|
+
# === Initializing
|
42
|
+
# Optionally, plugins can provide an initialization method for running any
|
43
|
+
# setup code needed by the plugin. This initialize method should be named as
|
44
|
+
# follows: "initializer_#{plugin_name}" where the name of the plugin is
|
45
|
+
# appended to the end of the initializer method name.
|
46
|
+
#
|
47
|
+
# If this method exists, it will be called automatically when plugins are
|
48
|
+
# loaded. The order of loading of initialization is not strictly defined, so
|
49
|
+
# do not rely on another plugin being initialized for your own plugin
|
50
|
+
# successfully initialize.
|
51
|
+
#
|
52
|
+
# == Usage
|
53
|
+
# LittlePlugger is used by extending your own class or module with the
|
54
|
+
# LittlePlugger module.
|
55
|
+
#
|
56
|
+
# module Logging
|
57
|
+
# extend LittlePlugger
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# This defines a +plugin_path+ and a +plugin_module+ for our Logging module.
|
61
|
+
# The +plugin_path+ is set to "logging/plugins", and therefore, the
|
62
|
+
# +plugin_modlue+ is defined as Logging::Plugins. All plugins for the
|
63
|
+
# Logging module should be found underneath this plugin module.
|
64
|
+
#
|
65
|
+
# The plugins for the Logging module are loaded and initialized by calling
|
66
|
+
# the +initialize_plugins+ method.
|
67
|
+
#
|
68
|
+
# Logging.initialize_plugins
|
69
|
+
#
|
70
|
+
# If you only want to load the plugin files but not initialize the plugin
|
71
|
+
# classes / modules then you can call the +load_plugins+ method.
|
72
|
+
#
|
73
|
+
# Logging.load_plugins
|
74
|
+
#
|
75
|
+
# Finally, you can get a hash of all the loaded plugins.
|
76
|
+
#
|
77
|
+
# Logging.plugins
|
78
|
+
#
|
79
|
+
# This returns a hash keyed by the plugin names with the plugin class /
|
80
|
+
# module as the value.
|
81
|
+
#
|
82
|
+
# If you only want a certain set of plugins to be loaded, then pass the
|
83
|
+
# names to the +plugin+ method.
|
84
|
+
#
|
85
|
+
# Logging.plugin :foo, :bar, :baz
|
86
|
+
#
|
87
|
+
# Now only three plugins for the Logging module will be loaded.
|
88
|
+
#
|
89
|
+
# === Customizing
|
90
|
+
# LittlePlugger allows the use of a custom plugin path and module. These are
|
91
|
+
# specified when extending with LilttlePlugger by passing the specific path
|
92
|
+
# and module to LittlePlugger.
|
93
|
+
#
|
94
|
+
# class Hoe
|
95
|
+
# extend LittlePlugger( :path => 'hoe', :module => Hoe )
|
96
|
+
#
|
97
|
+
# plugin(
|
98
|
+
# :clean, :debug, :deps, :flay, :flog, :package,
|
99
|
+
# :publish, :rcov, :signing, :test
|
100
|
+
# )
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# All ruby files found under the "hoe" directory will be treated as
|
104
|
+
# plugins, and the plugin classes / modules should reside directly under the
|
105
|
+
# Hoe namespace.
|
106
|
+
#
|
107
|
+
# We also specify a list of plugins to be loaded. Only these plugins will be
|
108
|
+
# loaded and initialized by the LittlePlugger module. The +plugin+ method
|
109
|
+
# can be called multiple times to add more plugins.
|
110
|
+
#
|
111
|
+
module Einhorn::Third::LittlePlugger
|
112
|
+
|
113
|
+
VERSION = '1.1.3' # :nodoc:
|
114
|
+
|
115
|
+
# Returns the version string for the library.
|
116
|
+
#
|
117
|
+
def self.version
|
118
|
+
VERSION
|
119
|
+
end
|
120
|
+
|
121
|
+
module ClassMethods
|
122
|
+
|
123
|
+
# Add the _names_ to the list of plugins that will be loaded.
|
124
|
+
#
|
125
|
+
def plugin( *names )
|
126
|
+
plugin_names.concat(names.map! {|n| n.to_sym})
|
127
|
+
end
|
128
|
+
|
129
|
+
# Add the _names_ to the list of plugins that will *not* be loaded. This
|
130
|
+
# list prevents the plugin system from loading unwanted or unneeded
|
131
|
+
# plugins.
|
132
|
+
#
|
133
|
+
# If a plugin name appears in both the 'disregard_plugin' list and the
|
134
|
+
# 'plugin' list, the disregard list takes precedence; that is, the plugin
|
135
|
+
# will not be loaded.
|
136
|
+
#
|
137
|
+
def disregard_plugin( *names )
|
138
|
+
@disregard_plugin ||= []
|
139
|
+
@disregard_plugin.concat(names.map! {|n| n.to_sym})
|
140
|
+
@disregard_plugin
|
141
|
+
end
|
142
|
+
alias :disregard_plugins :disregard_plugin
|
143
|
+
|
144
|
+
# Returns the array of plugin names that will be loaded. If the array is
|
145
|
+
# empty, then any plugin found in the +plugin_path+ will be loaded.
|
146
|
+
#
|
147
|
+
def plugin_names
|
148
|
+
@plugin_names ||= []
|
149
|
+
end
|
150
|
+
|
151
|
+
# Loads the desired plugins and returns a hash. The hash contains all
|
152
|
+
# the plugin classes and modules keyed by the plugin name.
|
153
|
+
#
|
154
|
+
def plugins
|
155
|
+
load_plugins
|
156
|
+
pm = plugin_module
|
157
|
+
names = pm.constants.map { |s| s.to_s }
|
158
|
+
names.reject! { |n| n =~ %r/^[A-Z_]+$/ }
|
159
|
+
|
160
|
+
h = {}
|
161
|
+
names.each do |name|
|
162
|
+
sym = ::Einhorn::Third::LittlePlugger.underscore(name).to_sym
|
163
|
+
next unless plugin_names.empty? or plugin_names.include? sym
|
164
|
+
next if disregard_plugins.include? sym
|
165
|
+
h[sym] = pm.const_get name
|
166
|
+
end
|
167
|
+
h
|
168
|
+
end
|
169
|
+
|
170
|
+
# Iterate over the loaded plugin classes and modules and call the
|
171
|
+
# initialize method for each plugin. The plugin's initialize method is
|
172
|
+
# defeind as +initialize_plugin_name+, where the plugin name is unique
|
173
|
+
# to each plugin.
|
174
|
+
#
|
175
|
+
def initialize_plugins
|
176
|
+
plugins.each do |name, klass|
|
177
|
+
msg = "initialize_#{name}"
|
178
|
+
klass.send msg if klass.respond_to? msg
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Iterate through all installed gems looking for those that have the
|
183
|
+
# +plugin_path+ in their "lib" folder, and load all .rb files found in
|
184
|
+
# the gem's plugin path. Each .rb file should define one class or module
|
185
|
+
# that will be used as a plugin.
|
186
|
+
#
|
187
|
+
def load_plugins
|
188
|
+
@loaded ||= {}
|
189
|
+
found = {}
|
190
|
+
|
191
|
+
Gem.find_files(File.join(plugin_path, '*.rb')).sort!.reverse_each do |path|
|
192
|
+
name = File.basename(path, '.rb').to_sym
|
193
|
+
found[name] = path unless found.key? name
|
194
|
+
end
|
195
|
+
|
196
|
+
:keep_on_truckin while found.map { |name, path|
|
197
|
+
next unless plugin_names.empty? or plugin_names.include? name
|
198
|
+
next if disregard_plugins.include? name
|
199
|
+
next if @loaded[name]
|
200
|
+
begin
|
201
|
+
@loaded[name] = load path
|
202
|
+
rescue ScriptError, StandardError => err
|
203
|
+
warn "Error loading #{path.inspect}: #{err.message}. skipping..."
|
204
|
+
end
|
205
|
+
}.any?
|
206
|
+
end
|
207
|
+
|
208
|
+
# The path to search in a gem's 'lib' folder for plugins.
|
209
|
+
#
|
210
|
+
def plugin_path
|
211
|
+
::Einhorn::Third::LittlePlugger.default_plugin_path(self)
|
212
|
+
end
|
213
|
+
|
214
|
+
# This module or class where plugins are located.
|
215
|
+
#
|
216
|
+
def plugin_module
|
217
|
+
::Einhorn::Third::LittlePlugger.default_plugin_module(plugin_path)
|
218
|
+
end
|
219
|
+
|
220
|
+
end # module ClassMethods
|
221
|
+
|
222
|
+
# :stopdoc:
|
223
|
+
|
224
|
+
# Called when another object extends itself with LittlePlugger.
|
225
|
+
#
|
226
|
+
def self.extended( other )
|
227
|
+
other.extend ClassMethods
|
228
|
+
end
|
229
|
+
|
230
|
+
# Convert the given string from camel case to snake case. Method liberally
|
231
|
+
# stolen from ActiveSupport.
|
232
|
+
#
|
233
|
+
# underscore( "FooBar" ) #=> "foo_bar"
|
234
|
+
#
|
235
|
+
def self.underscore( string )
|
236
|
+
string.to_s.
|
237
|
+
gsub(%r/::/, '/').
|
238
|
+
gsub(%r/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
239
|
+
gsub(%r/([a-z\d])([A-Z])/,'\1_\2').
|
240
|
+
tr('-', '_').
|
241
|
+
downcase
|
242
|
+
end
|
243
|
+
|
244
|
+
# For a given object returns a default plugin path. The path is
|
245
|
+
# created by splitting the object's class name on the namespace separator
|
246
|
+
# "::" and converting each part of the namespace into an underscored
|
247
|
+
# string (see the +underscore+ method). The strings are then joined using
|
248
|
+
# the File#join method to give a filesystem path. Appended to this path is
|
249
|
+
# the 'plugins' directory.
|
250
|
+
#
|
251
|
+
# default_plugin_path( FooBar::Baz ) #=> "foo_bar/baz/plugins"
|
252
|
+
#
|
253
|
+
def self.default_plugin_path( obj )
|
254
|
+
obj = obj.class unless obj.is_a? Module
|
255
|
+
File.join(underscore(obj.name), 'plugins')
|
256
|
+
end
|
257
|
+
|
258
|
+
# For a given path returns the class or module corresponding to the
|
259
|
+
# path. This method assumes a correspondence between directory names and
|
260
|
+
# Ruby namespaces.
|
261
|
+
#
|
262
|
+
# default_plugin_module( "foo_bar/baz/plugins" ) #=> FooBar::Baz::Plugins
|
263
|
+
#
|
264
|
+
# This method will fail if any of the namespaces have not yet been
|
265
|
+
# defined.
|
266
|
+
#
|
267
|
+
def self.default_plugin_module( path )
|
268
|
+
path.split(File::SEPARATOR).inject(Object) do |mod, const|
|
269
|
+
const = const.split('_').map { |s| s.capitalize }.join
|
270
|
+
mod.const_get const
|
271
|
+
end
|
272
|
+
end
|
273
|
+
# :startdoc:
|
274
|
+
|
275
|
+
end # module Einhorn::Third::LittlePlugger
|
276
|
+
|
277
|
+
|
278
|
+
module Kernel
|
279
|
+
module Einhorn::Third
|
280
|
+
|
281
|
+
# call-seq:
|
282
|
+
# LittlePlugger( opts = {} )
|
283
|
+
#
|
284
|
+
# This method allows the user to override some of LittlePlugger's default
|
285
|
+
# settings when mixed into a module or class.
|
286
|
+
#
|
287
|
+
# See the "Customizing" section of the LittlePlugger documentation for an
|
288
|
+
# example of how this method is used.
|
289
|
+
#
|
290
|
+
# ==== Options
|
291
|
+
#
|
292
|
+
# * :path <String>
|
293
|
+
# The default plugin path. Defaults to "module_name/plugins".
|
294
|
+
#
|
295
|
+
# * :module <Module>
|
296
|
+
# The module where plugins will be loaded. Defaults to
|
297
|
+
# ModuleName::Plugins.
|
298
|
+
#
|
299
|
+
# * :plugins <Array>
|
300
|
+
# The array of default plugins to load. Only the plugins listed in this
|
301
|
+
# array will be loaded by LittlePlugger.
|
302
|
+
#
|
303
|
+
def LittlePlugger( opts = {} )
|
304
|
+
return ::Einhorn::LittlePlugger::ClassMethods if opts.empty?
|
305
|
+
Module.new {
|
306
|
+
include ::Einhorn::LittlePlugger::ClassMethods
|
307
|
+
|
308
|
+
if opts.key?(:path)
|
309
|
+
eval %Q{def plugin_path() #{opts[:path].to_s.inspect} end}
|
310
|
+
end
|
311
|
+
|
312
|
+
if opts.key?(:module)
|
313
|
+
eval %Q{def plugin_module() #{opts[:module].name} end}
|
314
|
+
end
|
315
|
+
|
316
|
+
if opts.key?(:plugins)
|
317
|
+
plugins = Array(opts[:plugins]).map {|val| val.to_sym.inspect}.join(',')
|
318
|
+
eval %Q{def plugin_names() @plugin_names ||= [#{plugins}] end}
|
319
|
+
end
|
320
|
+
}
|
321
|
+
end
|
322
|
+
end # module Einhorn::Third
|
323
|
+
end # module Kernel
|
324
|
+
|
325
|
+
# EOF
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
3
|
+
|
4
|
+
describe LittlePlugger do
|
5
|
+
|
6
|
+
it "converts a string from camel-case to underscore" do
|
7
|
+
LittlePlugger.underscore('FooBarBaz').should be == 'foo_bar_baz'
|
8
|
+
LittlePlugger.underscore('CouchDB').should be == 'couch_db'
|
9
|
+
LittlePlugger.underscore('FOOBar').should be == 'foo_bar'
|
10
|
+
LittlePlugger.underscore('Foo::Bar::BazBuz').should be == 'foo/bar/baz_buz'
|
11
|
+
end
|
12
|
+
|
13
|
+
it "generates a default plugin path" do
|
14
|
+
LittlePlugger.default_plugin_path(LittlePlugger).should be == 'little_plugger/plugins'
|
15
|
+
LittlePlugger.default_plugin_path(Process::Status).should be == 'process/status/plugins'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "generates a default plugin module" do
|
19
|
+
LittlePlugger.default_plugin_module('little_plugger').should be == LittlePlugger
|
20
|
+
lambda {LittlePlugger.default_plugin_module('little_plugger/plugins')}.
|
21
|
+
should raise_error(NameError, 'uninitialized constant LittlePlugger::Plugins')
|
22
|
+
LittlePlugger.default_plugin_module('process/status').should be == Process::Status
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# EOF
|
data/lib/einhorn/version.rb
CHANGED
data/test/_lib.rb
ADDED
data/test/unit/einhorn.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../_lib'))
|
2
2
|
|
3
3
|
require 'einhorn'
|
4
4
|
|
5
|
-
class EinhornTest <
|
6
|
-
|
7
|
-
|
5
|
+
class EinhornTest < EinhornTestCase
|
6
|
+
describe "when sockifying" do
|
7
|
+
after do
|
8
8
|
Einhorn::State.sockets = {}
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
it "correctly parses srv: arguments" do
|
12
12
|
cmd = ['foo', 'srv:1.2.3.4:123,llama,test', 'bar']
|
13
13
|
Einhorn.expects(:bind).once.with('1.2.3.4', '123', ['llama', 'test']).returns(4)
|
14
14
|
|
@@ -17,7 +17,7 @@ class EinhornTest < Test::Unit::TestCase
|
|
17
17
|
assert_equal(['foo', '4', 'bar'], cmd)
|
18
18
|
end
|
19
19
|
|
20
|
-
|
20
|
+
it "correctly parses --opt=srv: arguments" do
|
21
21
|
cmd = ['foo', '--opt=srv:1.2.3.4:456', 'baz']
|
22
22
|
Einhorn.expects(:bind).once.with('1.2.3.4', '456', []).returns(5)
|
23
23
|
|
@@ -26,7 +26,7 @@ class EinhornTest < Test::Unit::TestCase
|
|
26
26
|
assert_equal(['foo', '--opt=5', 'baz'], cmd)
|
27
27
|
end
|
28
28
|
|
29
|
-
|
29
|
+
it "uses the same fd number for the same server spec" do
|
30
30
|
cmd = ['foo', '--opt=srv:1.2.3.4:8910', 'srv:1.2.3.4:8910']
|
31
31
|
Einhorn.expects(:bind).once.with('1.2.3.4', '8910', []).returns(10)
|
32
32
|
|
@@ -36,8 +36,8 @@ class EinhornTest < Test::Unit::TestCase
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
describe '.update_state' do
|
40
|
+
it 'correctly updates keys to match new default state hash' do
|
41
41
|
Einhorn::State.stubs(:default_state).returns(:baz => 23, :foo => 1)
|
42
42
|
old_state = {:foo => 2, :bar => 2}
|
43
43
|
|
@@ -46,7 +46,7 @@ class EinhornTest < Test::Unit::TestCase
|
|
46
46
|
assert_match(/State format has changed/, message)
|
47
47
|
end
|
48
48
|
|
49
|
-
|
49
|
+
it 'does not change the state if the format has not changed' do
|
50
50
|
Einhorn::State.stubs(:default_state).returns(:baz => 23, :foo => 1)
|
51
51
|
old_state = {:baz => 14, :foo => 1234}
|
52
52
|
|
data/test/unit/einhorn/client.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../../
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../_lib'))
|
2
2
|
|
3
3
|
require 'einhorn'
|
4
4
|
|
5
|
-
class ClientTest <
|
5
|
+
class ClientTest < EinhornTestCase
|
6
6
|
def unserialized_message
|
7
7
|
{:foo => ['%bar', '%baz']}
|
8
8
|
end
|
@@ -15,8 +15,8 @@ class ClientTest < Test::Unit::TestCase
|
|
15
15
|
"---%0A:foo:%0A- ! '%25bar'%0A- ! '%25baz'%0A\n"
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
18
|
+
describe "when sending a message" do
|
19
|
+
it "writes a serialized line" do
|
20
20
|
socket = mock
|
21
21
|
socket.expects(:write).with do |write|
|
22
22
|
write == serialized_1_8 || write == serialized_1_9
|
@@ -25,15 +25,15 @@ class ClientTest < Test::Unit::TestCase
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
28
|
+
describe "when receiving a message" do
|
29
|
+
it "deserializes a single 1.8-style line" do
|
30
30
|
socket = mock
|
31
31
|
socket.expects(:readline).returns(serialized_1_8)
|
32
32
|
result = Einhorn::Client::Transport.receive_message(socket)
|
33
33
|
assert_equal(result, unserialized_message)
|
34
34
|
end
|
35
35
|
|
36
|
-
|
36
|
+
it "deserializes a single 1.9-style line" do
|
37
37
|
socket = mock
|
38
38
|
socket.expects(:readline).returns(serialized_1_9)
|
39
39
|
result = Einhorn::Client::Transport.receive_message(socket)
|
@@ -41,23 +41,23 @@ class ClientTest < Test::Unit::TestCase
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
|
45
|
-
|
44
|
+
describe "when {de,}serializing a message" do
|
45
|
+
it "serializes and escape a message as expected" do
|
46
46
|
actual = Einhorn::Client::Transport.serialize_message(unserialized_message)
|
47
47
|
assert(actual == serialized_1_8 || actual == serialized_1_9, "Actual message is #{actual.inspect}")
|
48
48
|
end
|
49
49
|
|
50
|
-
|
50
|
+
it "deserializes and unescape a 1.8-style message as expected" do
|
51
51
|
actual = Einhorn::Client::Transport.deserialize_message(serialized_1_8)
|
52
52
|
assert_equal(unserialized_message, actual)
|
53
53
|
end
|
54
54
|
|
55
|
-
|
55
|
+
it "deserializes and unescape a 1.9-style message as expected" do
|
56
56
|
actual = Einhorn::Client::Transport.deserialize_message(serialized_1_9)
|
57
57
|
assert_equal(unserialized_message, actual)
|
58
58
|
end
|
59
59
|
|
60
|
-
|
60
|
+
it "raises an error when deserializing invalid YAML" do
|
61
61
|
invalid_serialized = "-%0A\t-"
|
62
62
|
expected = [ArgumentError]
|
63
63
|
expected << Psych::SyntaxError if defined?(Psych::SyntaxError) # 1.9
|
@@ -1,18 +1,18 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../../
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../_lib'))
|
2
2
|
|
3
3
|
require 'einhorn'
|
4
4
|
|
5
|
-
class CommandTest <
|
5
|
+
class CommandTest < EinhornTestCase
|
6
6
|
include Einhorn
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
describe "when running quieter" do
|
9
|
+
it "increases the verbosity threshold" do
|
10
10
|
Einhorn::State.stubs(:verbosity => 1)
|
11
11
|
Einhorn::State.expects(:verbosity=).once.with(2).returns(2)
|
12
12
|
Command.quieter
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
it "maxes out at 2" do
|
16
16
|
Einhorn::State.stubs(:verbosity => 2)
|
17
17
|
Einhorn::State.expects(:verbosity=).never
|
18
18
|
Command.quieter
|
@@ -1,12 +1,12 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../../../
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../../_lib'))
|
2
2
|
|
3
3
|
require 'einhorn'
|
4
4
|
|
5
|
-
class InterfaceTest <
|
5
|
+
class InterfaceTest < EinhornTestCase
|
6
6
|
include Einhorn::Command
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
describe "when a command is received" do
|
9
|
+
it "calls that command" do
|
10
10
|
conn = stub(:log_debug => nil)
|
11
11
|
conn.expects(:write).once.with do |message|
|
12
12
|
# Remove trailing newline
|
@@ -22,8 +22,8 @@ class InterfaceTest < Test::Unit::TestCase
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
describe "when an unrecognized command is received" do
|
26
|
+
it "calls the unrecognized_command method" do
|
27
27
|
conn = stub(:log_debug => nil)
|
28
28
|
Interface.expects(:unrecognized_command).once
|
29
29
|
request = {
|
@@ -33,8 +33,8 @@ class InterfaceTest < Test::Unit::TestCase
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
describe "when a worker ack is received" do
|
37
|
+
it "registers ack and close the connection" do
|
38
38
|
conn = stub(:log_debug => nil)
|
39
39
|
conn.expects(:close).once
|
40
40
|
conn.expects(:write).never
|
data/test/unit/einhorn/event.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../../
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../_lib'))
|
2
2
|
|
3
3
|
require 'set'
|
4
4
|
require 'einhorn'
|
@@ -13,17 +13,17 @@ module Einhorn::Event
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
-
class EventTest <
|
17
|
-
|
18
|
-
|
16
|
+
class EventTest < EinhornTestCase
|
17
|
+
describe "when running the event loop" do
|
18
|
+
before do
|
19
19
|
Einhorn::Event.reset
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
after do
|
23
23
|
Einhorn::Event.reset
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
it "selects on readable descriptors" do
|
27
27
|
sock1 = mock(:fileno => 4)
|
28
28
|
sock2 = mock(:fileno => 5)
|
29
29
|
|
@@ -40,7 +40,7 @@ class EventTest < Test::Unit::TestCase
|
|
40
40
|
Einhorn::Event.loop_once
|
41
41
|
end
|
42
42
|
|
43
|
-
|
43
|
+
it "selects on writeable descriptors" do
|
44
44
|
sock1 = mock(:fileno => 4)
|
45
45
|
sock2 = mock(:fileno => 5)
|
46
46
|
|
@@ -60,7 +60,7 @@ class EventTest < Test::Unit::TestCase
|
|
60
60
|
Einhorn::Event.loop_once
|
61
61
|
end
|
62
62
|
|
63
|
-
|
63
|
+
it "runs callbacks for ready selectables" do
|
64
64
|
sock1 = mock(:fileno => 4)
|
65
65
|
sock2 = mock(:fileno => 5)
|
66
66
|
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require File.expand_path(File.join(File.dirname(__FILE__), '../../
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '../../_lib'))
|
2
2
|
|
3
3
|
require 'einhorn'
|
4
4
|
|
5
|
-
class WorkerPoolTest <
|
5
|
+
class WorkerPoolTest < EinhornTestCase
|
6
6
|
def stub_children
|
7
7
|
Einhorn::State.stubs(:children).returns(
|
8
8
|
1234 => {:type => :worker, :signaled => Set.new(['INT'])},
|
@@ -11,12 +11,12 @@ class WorkerPoolTest < Test::Unit::TestCase
|
|
11
11
|
)
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
describe "#workers_with_state" do
|
15
|
+
before do
|
16
16
|
stub_children
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
it "selects only the workers" do
|
20
20
|
workers_with_state = Einhorn::WorkerPool.workers_with_state
|
21
21
|
# Sort only needed for Ruby 1.8
|
22
22
|
assert_equal([
|
@@ -26,12 +26,12 @@ class WorkerPoolTest < Test::Unit::TestCase
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
|
30
|
-
|
29
|
+
describe "#unsignaled_workers" do
|
30
|
+
before do
|
31
31
|
stub_children
|
32
32
|
end
|
33
33
|
|
34
|
-
|
34
|
+
it "selects unsignaled workers" do
|
35
35
|
unsignaled_workers = Einhorn::WorkerPool.unsignaled_workers
|
36
36
|
assert_equal([1236], unsignaled_workers)
|
37
37
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: einhorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.8
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-07-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -28,37 +28,37 @@ dependencies:
|
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '0'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
31
|
+
name: minitest
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
33
33
|
none: false
|
34
34
|
requirements:
|
35
|
-
- -
|
35
|
+
- - <
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version:
|
37
|
+
version: '5.0'
|
38
38
|
type: :development
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
42
|
requirements:
|
43
|
-
- -
|
43
|
+
- - <
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
45
|
+
version: '5.0'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: mocha
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
49
49
|
none: false
|
50
50
|
requirements:
|
51
|
-
- -
|
51
|
+
- - ~>
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: '0'
|
53
|
+
version: '0.13'
|
54
54
|
type: :development
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
57
|
none: false
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
61
|
+
version: '0.13'
|
62
62
|
description: Einhorn makes it easy to run multiple instances of an application server,
|
63
63
|
all listening on the same port. You can also seamlessly restart your workers without
|
64
64
|
dropping any requests. Einhorn requires minimal application-level support, making
|
@@ -81,6 +81,7 @@ files:
|
|
81
81
|
- bin/einhorn
|
82
82
|
- bin/einhornsh
|
83
83
|
- einhorn.gemspec
|
84
|
+
- example/plugin.rb
|
84
85
|
- example/pool_worker.rb
|
85
86
|
- example/thin_example
|
86
87
|
- example/time_server
|
@@ -96,10 +97,19 @@ files:
|
|
96
97
|
- lib/einhorn/event/loop_breaker.rb
|
97
98
|
- lib/einhorn/event/persistent.rb
|
98
99
|
- lib/einhorn/event/timer.rb
|
100
|
+
- lib/einhorn/third.rb
|
101
|
+
- lib/einhorn/third/little-plugger.rb
|
102
|
+
- lib/einhorn/third/little-plugger/.gitignore
|
103
|
+
- lib/einhorn/third/little-plugger/History.txt
|
104
|
+
- lib/einhorn/third/little-plugger/README.rdoc
|
105
|
+
- lib/einhorn/third/little-plugger/Rakefile
|
106
|
+
- lib/einhorn/third/little-plugger/lib/little-plugger.rb
|
107
|
+
- lib/einhorn/third/little-plugger/spec/little-plugger_spec.rb
|
108
|
+
- lib/einhorn/third/little-plugger/spec/spec_helper.rb
|
99
109
|
- lib/einhorn/version.rb
|
100
110
|
- lib/einhorn/worker.rb
|
101
111
|
- lib/einhorn/worker_pool.rb
|
102
|
-
- test/
|
112
|
+
- test/_lib.rb
|
103
113
|
- test/unit/einhorn.rb
|
104
114
|
- test/unit/einhorn/client.rb
|
105
115
|
- test/unit/einhorn/command.rb
|
@@ -131,11 +141,10 @@ signing_key:
|
|
131
141
|
specification_version: 3
|
132
142
|
summary: ! 'Einhorn: the language-independent shared socket manager'
|
133
143
|
test_files:
|
134
|
-
- test/
|
144
|
+
- test/_lib.rb
|
135
145
|
- test/unit/einhorn.rb
|
136
146
|
- test/unit/einhorn/client.rb
|
137
147
|
- test/unit/einhorn/command.rb
|
138
148
|
- test/unit/einhorn/command/interface.rb
|
139
149
|
- test/unit/einhorn/event.rb
|
140
150
|
- test/unit/einhorn/worker_pool.rb
|
141
|
-
has_rdoc:
|