classx-pluggable 0.0.1
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.
- data/ChangeLog +221 -0
- data/README +88 -0
- data/Rakefile +52 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-attribute_rb.html +925 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-attributes_rb.html +772 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-bracketable_rb.html +671 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-role-logger_rb.html +716 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx-validate_rb.html +663 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-classx-0_0_5-lib-classx_rb.html +820 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs-block_rb.html +661 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs-callbacks_rb.html +932 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs-change_rb.html +779 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs-hunk_rb.html +867 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-diff-lcs-1_1_2-lib-diff-lcs_rb.html +1715 -0
- data/doc/output/coverage/-Library-Ruby-Gems-gems-rcov-0_8_1_2_0-lib-rcov_rb.html +1598 -0
- data/doc/output/coverage/examples-test_runner-bin-test_runner_rb.html +628 -0
- data/doc/output/coverage/examples-test_runner-lib-test_runner-plugin-setup_fixture_rb.html +641 -0
- data/doc/output/coverage/examples-test_runner-lib-test_runner-plugin-test_info_rb.html +638 -0
- data/doc/output/coverage/examples-test_runner-lib-test_runner-plugin-test_timer_rb.html +666 -0
- data/doc/output/coverage/examples-test_runner-lib-test_runner_rb.html +643 -0
- data/doc/output/coverage/index.html +711 -0
- data/doc/output/coverage/lib-classx-pluggable-plugin_rb.html +676 -0
- data/doc/output/coverage/lib-classx-pluggable_rb.html +841 -0
- data/examples/test_runner/bin/test_runner.rb +18 -0
- data/examples/test_runner/conf/config.yaml +19 -0
- data/examples/test_runner/lib/test_runner.rb +33 -0
- data/examples/test_runner/lib/test_runner/plugin/setup_fixture.rb +31 -0
- data/examples/test_runner/lib/test_runner/plugin/test_info.rb +28 -0
- data/examples/test_runner/lib/test_runner/plugin/test_timer.rb +56 -0
- data/lib/classx/pluggable.rb +231 -0
- data/lib/classx/pluggable/plugin.rb +66 -0
- data/spec/classx-pluggable-util/module2path_spec.rb +30 -0
- data/spec/classx-pluggable-util/nested_autoload_spec.rb +48 -0
- data/spec/classx-pluggable-util/nested_const_get_spec.rb +48 -0
- data/spec/classx-pluggable/component_class_get.rb +18 -0
- data/spec/classx-pluggable_spec.rb +5 -0
- data/spec/example_spec.rb +20 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +6 -0
- data/tasks/basic_config.rake +22 -0
- data/tasks/basic_tasks.rake +139 -0
- metadata +127 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
|
2
|
+
|
3
|
+
require 'test_runner'
|
4
|
+
|
5
|
+
require 'yaml'
|
6
|
+
conf = ARGV[0] || File.join(File.dirname(__FILE__), '..', 'conf/config.yaml')
|
7
|
+
|
8
|
+
config = nil
|
9
|
+
File.open(conf) do |f|
|
10
|
+
config = YAML.load(f.read)
|
11
|
+
end
|
12
|
+
app = TestRunner.new(config["global"].merge({
|
13
|
+
:test_cases => [:foo, :bar, :baz],
|
14
|
+
:logger => $logger ? $logger : Logger.new($stderr) # FIXME: It's for spec/example_spec.rb
|
15
|
+
}))
|
16
|
+
app.load_plugins(config["plugins"])
|
17
|
+
|
18
|
+
app.run
|
@@ -0,0 +1,19 @@
|
|
1
|
+
---
|
2
|
+
global:
|
3
|
+
log_level: debug
|
4
|
+
check_events: true
|
5
|
+
events:
|
6
|
+
- BEFORE_EACH
|
7
|
+
- AROUND_ALL
|
8
|
+
- AROUND_EACH
|
9
|
+
|
10
|
+
plugins:
|
11
|
+
- module: TestRunner::Plugin::SetupFixture #=> autoloading TestRunner::Plugin::SetupFixture, "test_runner/plugin/setup_fixture"
|
12
|
+
- module: +TestTimer # same means of TestRunner::Plugin::TestTimer #=> autoloading TestRunner::Plugin::TestTimer, "test_runner/plugin/test_timer"
|
13
|
+
- module: +TestInfo
|
14
|
+
config:
|
15
|
+
template: start test %s
|
16
|
+
- module: +TestInfo
|
17
|
+
config:
|
18
|
+
template: you can also ouput other info for %s
|
19
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'classx'
|
2
|
+
require 'classx/validate'
|
3
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../../lib')))
|
4
|
+
require 'classx/pluggable'
|
5
|
+
require 'classx/pluggable/plugin'
|
6
|
+
|
7
|
+
class TestRunner
|
8
|
+
include ClassX
|
9
|
+
include ClassX::Role::Logger
|
10
|
+
include ClassX::Pluggable
|
11
|
+
|
12
|
+
module Plugin; end
|
13
|
+
|
14
|
+
has :test_cases
|
15
|
+
|
16
|
+
def run
|
17
|
+
call_event_around(:ALL, :logger => logger) do
|
18
|
+
|
19
|
+
test_cases.each do |tc|
|
20
|
+
_do_test(tc)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# it's dummy
|
27
|
+
def _do_test tc
|
28
|
+
call_event_around(:EACH, :logger => logger, :test => tc) do
|
29
|
+
sleep(0.1)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'classx'
|
2
|
+
require 'classx/validate'
|
3
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../../../lib')))
|
4
|
+
require 'classx/pluggable/plugin'
|
5
|
+
|
6
|
+
class TestRunner
|
7
|
+
module Plugin
|
8
|
+
class SetupFixture
|
9
|
+
include ClassX
|
10
|
+
include ClassX::Pluggable::Plugin
|
11
|
+
include ClassX::Pluggable::Plugin::AutoRegister
|
12
|
+
|
13
|
+
# without C::P::P::AutoRegister
|
14
|
+
# you can do followings:
|
15
|
+
#
|
16
|
+
# define_events({
|
17
|
+
# :AROUND_ALL => :on_around_all,
|
18
|
+
# })
|
19
|
+
|
20
|
+
def on_around_all param
|
21
|
+
param = ClassX::Validate.validate param do
|
22
|
+
has :logger
|
23
|
+
end
|
24
|
+
|
25
|
+
param.logger.info "#{self.class}: Setup Fixture"
|
26
|
+
yield
|
27
|
+
param.logger.info "#{self.class}: Clear Fixture"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'classx'
|
2
|
+
require 'classx/validate'
|
3
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../../../lib')))
|
4
|
+
require 'classx/pluggable/plugin'
|
5
|
+
|
6
|
+
class TestRunner
|
7
|
+
module Plugin
|
8
|
+
class TestInfo
|
9
|
+
include ClassX
|
10
|
+
include ClassX::Pluggable::Plugin
|
11
|
+
include ClassX::Pluggable::Plugin::AutoRegister
|
12
|
+
|
13
|
+
has :template
|
14
|
+
|
15
|
+
def on_before_each param
|
16
|
+
param = ClassX::Validate.validate param do
|
17
|
+
has :logger
|
18
|
+
has :test
|
19
|
+
end
|
20
|
+
|
21
|
+
info = self.template % [ param.test ]
|
22
|
+
param.logger.info "#{self.class}: #{info}"
|
23
|
+
|
24
|
+
info
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'classx'
|
2
|
+
require 'classx/validate'
|
3
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../../../lib')))
|
4
|
+
require 'classx/pluggable/plugin'
|
5
|
+
|
6
|
+
|
7
|
+
class TestRunner
|
8
|
+
module Plugin
|
9
|
+
class TestTimer
|
10
|
+
include ClassX
|
11
|
+
include ClassX::Pluggable::Plugin
|
12
|
+
include ClassX::Pluggable::Plugin::AutoRegister
|
13
|
+
|
14
|
+
# without C::P::P::AutoRegister
|
15
|
+
# you can do followings:
|
16
|
+
#
|
17
|
+
# define_events({
|
18
|
+
# :AROUND_ALL => :on_around_all,
|
19
|
+
# :AROUND_EACH => :on_arond_each,
|
20
|
+
# })
|
21
|
+
|
22
|
+
def on_around_all param
|
23
|
+
param = ClassX::Validate.validate param do
|
24
|
+
has :logger
|
25
|
+
end
|
26
|
+
|
27
|
+
param.logger.info "#{self.class}: total: start timer"
|
28
|
+
test_suite_timer = Time.now
|
29
|
+
|
30
|
+
yield
|
31
|
+
|
32
|
+
diff = Time.now - test_suite_timer
|
33
|
+
param.logger.info "#{self.class}: total: #{diff.to_f} sec."
|
34
|
+
|
35
|
+
diff
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_around_each param
|
39
|
+
param = ClassX::Validate.validate param do
|
40
|
+
has :logger
|
41
|
+
has :test
|
42
|
+
end
|
43
|
+
|
44
|
+
param.logger.info "#{self.class}: test #{param.test}: start timer"
|
45
|
+
test_timer = Time.now
|
46
|
+
yield
|
47
|
+
|
48
|
+
diff = Time.now - test_timer
|
49
|
+
|
50
|
+
param.logger.info "#{self.class}: test #{param.test}: #{diff.to_f} sec."
|
51
|
+
|
52
|
+
diff
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'classx'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module ClassX
|
5
|
+
# in your context class.
|
6
|
+
#
|
7
|
+
# require 'classx'
|
8
|
+
# require 'classx/pluggable'
|
9
|
+
# class YourApp
|
10
|
+
# include ClassX
|
11
|
+
# include ClassX::Pluggable
|
12
|
+
#
|
13
|
+
# def run
|
14
|
+
# call_event("SETUP", {})
|
15
|
+
# # you app
|
16
|
+
# call_event("TEARDOWN", {})
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# in your plugin class
|
22
|
+
#
|
23
|
+
# require 'classx'
|
24
|
+
# require 'classx/pluggable'
|
25
|
+
# class YourApp
|
26
|
+
# class Plugin
|
27
|
+
# include ClassX
|
28
|
+
# include ClassX::Pluggable::Plugin
|
29
|
+
#
|
30
|
+
# class SomePlugin < Plugin
|
31
|
+
# def register
|
32
|
+
# add_event("SETUP", :on_setup)
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# def on_setup param
|
36
|
+
# # param is Hash
|
37
|
+
# # hooked setup
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
module Pluggable
|
44
|
+
extend ClassX::Attributes
|
45
|
+
extend ClassX::Role::Logger
|
46
|
+
|
47
|
+
class PluginLoadError < ::Exception; end
|
48
|
+
|
49
|
+
has :__classx_pluggable_events_of,
|
50
|
+
:lazy => true,
|
51
|
+
:no_cmd_option => true,
|
52
|
+
:default => proc {|mine| mine.events.inject({}) {|h,event| h[event] = []; h } }
|
53
|
+
|
54
|
+
has :plugin_dir,
|
55
|
+
:optional => true,
|
56
|
+
:kind_of => Array
|
57
|
+
|
58
|
+
has :check_events,
|
59
|
+
:default => false,
|
60
|
+
:optional => true,
|
61
|
+
:desc => "only add events that define before add_event."
|
62
|
+
|
63
|
+
has :events,
|
64
|
+
:lazy => true,
|
65
|
+
:optional => true,
|
66
|
+
:kind_of => Array,
|
67
|
+
:desc => "hook point for #{self}'s instance",
|
68
|
+
:default => proc { [] }
|
69
|
+
|
70
|
+
# register plugin and method to hook point.
|
71
|
+
#
|
72
|
+
def add_event name, plugin, meth
|
73
|
+
name = name.to_s
|
74
|
+
if self.check_events && !self.events.include?(name)
|
75
|
+
raise "#{name.inspect} should be declared before call add_event. not in #{self.events.inspect}"
|
76
|
+
else
|
77
|
+
self.__classx_pluggable_events_of[name] ||= []
|
78
|
+
end
|
79
|
+
self.__classx_pluggable_events_of[name] << { :plugin => plugin, :method => meth }
|
80
|
+
end
|
81
|
+
|
82
|
+
# load plugins.
|
83
|
+
#
|
84
|
+
# app.load_plugins([
|
85
|
+
# { :module => "YourApp::Plugin::Foo", :confiig => { :some_config => "foo"} },
|
86
|
+
# { :module => "+Bar", :confiig => { } }, # It's same meaning of YourApp::Plugin::Bar
|
87
|
+
# ])
|
88
|
+
#
|
89
|
+
def load_plugins plugins
|
90
|
+
load_components("plugin", plugins)
|
91
|
+
end
|
92
|
+
|
93
|
+
# if you customize Plugin name space. you can use this instead of +load_plugins+.
|
94
|
+
#
|
95
|
+
# app.load_components('engine', [
|
96
|
+
# { :module => "YourApp::Engine::Foo", :confiig => { :some_config => "foo"} },
|
97
|
+
# { :module => "+Bar", :confiig => { } }, # It's same meaning of YourApp::Engine::Bar
|
98
|
+
# ])
|
99
|
+
#
|
100
|
+
def load_components type, components
|
101
|
+
components.each do |component|
|
102
|
+
load_component type, component
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def load_component type, hash
|
107
|
+
component = OpenStruct.new(hash.dup)
|
108
|
+
mod = self.class.component_class_get(type, component.module, { :plugin_dir => self.plugin_dir })
|
109
|
+
component.config ||= {}
|
110
|
+
component.config[:context] = self
|
111
|
+
instance = mod.new(component.config)
|
112
|
+
instance.register
|
113
|
+
logger.debug("ClassX::Pluggable: loaded #{type} #{component.module}, config=#{instance.inspect}")
|
114
|
+
end
|
115
|
+
|
116
|
+
# invoke registered event of +name+ with +args+. and return array of result each callback.
|
117
|
+
def call_event name, *args
|
118
|
+
name = name.to_s
|
119
|
+
if events = self.__classx_pluggable_events_of[name]
|
120
|
+
events.map do |event|
|
121
|
+
event[:plugin].__send__(event[:method], *args)
|
122
|
+
end
|
123
|
+
else
|
124
|
+
[]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# invoke registered event of BEFORE_xxxx and yield block and invoke hook AFTER_xxxx.
|
129
|
+
def call_event_around name, *args, &block
|
130
|
+
name = name.to_s
|
131
|
+
around_name = "AROUND_#{name}"
|
132
|
+
|
133
|
+
call_event("BEFORE_#{name}", *args)
|
134
|
+
if events = self.__classx_pluggable_events_of[around_name]
|
135
|
+
procs = []
|
136
|
+
procs << block
|
137
|
+
index = 0
|
138
|
+
nested_proc = events.inject(block) {|bl, event| proc { event[:plugin].__send__(event[:method], *args, &bl ) } }
|
139
|
+
nested_proc.call
|
140
|
+
end
|
141
|
+
call_event("AFTER_#{name}", *args)
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
module Util
|
147
|
+
def module2path mod
|
148
|
+
mod.split(/::/).map { |s|
|
149
|
+
s.gsub(/([A-Z][a-z]+)(?=[A-Z][a-z]*?)/, '\1_').gsub(/([A-Z])(?=[A-Z][a-z]+)/, '\1_').downcase
|
150
|
+
}.join(File::SEPARATOR)
|
151
|
+
end
|
152
|
+
|
153
|
+
def nested_const_get mod
|
154
|
+
name_spaces = mod.split(/::/)
|
155
|
+
result = ::Object
|
156
|
+
name_spaces.each do |const|
|
157
|
+
result = result.const_get(const)
|
158
|
+
end
|
159
|
+
return result
|
160
|
+
end
|
161
|
+
|
162
|
+
def nested_autoload mod, path
|
163
|
+
name_spaces = mod.split(/::/)
|
164
|
+
target = name_spaces.pop
|
165
|
+
tmp = ::Object
|
166
|
+
name_spaces.each do |const|
|
167
|
+
tmp = tmp.const_get(const)
|
168
|
+
end
|
169
|
+
tmp.autoload(target, path)
|
170
|
+
end
|
171
|
+
|
172
|
+
module_function :module2path, :nested_const_get, :nested_autoload
|
173
|
+
end
|
174
|
+
|
175
|
+
module ClassMethods
|
176
|
+
include ClassX::Pluggable::Util
|
177
|
+
|
178
|
+
def component_class_get type, name, options={}
|
179
|
+
case name
|
180
|
+
when ::Class
|
181
|
+
return name
|
182
|
+
else
|
183
|
+
mod_name = nil
|
184
|
+
target_name = nil
|
185
|
+
if name =~ /\A\+([\w:]+)\z/
|
186
|
+
target_name = $1
|
187
|
+
mod_name = [ self, type.capitalize, target_name ].join("::")
|
188
|
+
else
|
189
|
+
mod_name = name
|
190
|
+
end
|
191
|
+
begin
|
192
|
+
return nested_const_get(mod_name)
|
193
|
+
rescue NameError => e
|
194
|
+
begin
|
195
|
+
if options[:plugin_dir]
|
196
|
+
options[:plugin_dir].each do |path|
|
197
|
+
begin
|
198
|
+
begin
|
199
|
+
self.const_get(type.capitalize).autoload(name, File.expand_path(File.join(path, module2path(target_name))))
|
200
|
+
rescue LoadError => e
|
201
|
+
raise ::ClassX::Pluggable::PluginLoadError, "class: #{mod_name} is not found"
|
202
|
+
end
|
203
|
+
return nested_const_get(mod_name)
|
204
|
+
rescue NameError => e
|
205
|
+
next
|
206
|
+
end
|
207
|
+
raise NameError, "must not happened unless your code is something wrong!!"
|
208
|
+
end
|
209
|
+
else
|
210
|
+
nested_autoload(mod_name, module2path(mod_name))
|
211
|
+
nested_const_get mod_name
|
212
|
+
end
|
213
|
+
rescue LoadError => e
|
214
|
+
raise ::ClassX::Pluggable::PluginLoadError, "class: #{mod_name} is not found."
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.included klass
|
222
|
+
klass.extend ClassMethods
|
223
|
+
end
|
224
|
+
|
225
|
+
# It's useful for testing.
|
226
|
+
class MockContext
|
227
|
+
include ClassX
|
228
|
+
include ClassX::Pluggable
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'classx'
|
2
|
+
|
3
|
+
module ClassX
|
4
|
+
module Pluggable
|
5
|
+
module Plugin
|
6
|
+
extend ClassX::Attributes
|
7
|
+
|
8
|
+
has :context, :kind_of => ClassX::Pluggable
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def define_events hash
|
12
|
+
define_method :register do
|
13
|
+
add_events hash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Abstract method for calling from context instance automatically that you should implement like followings:
|
19
|
+
#
|
20
|
+
# def register
|
21
|
+
# add_event('SOME_EVENT', 'on_some_event')
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def on_some_event
|
25
|
+
# # do something.
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
def register
|
29
|
+
raise NotImprementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
def inspect
|
33
|
+
hash = self.to_hash
|
34
|
+
hash.delete("context")
|
35
|
+
"#{self.class}: #{hash.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.included klass
|
41
|
+
klass.extend(ClassMethods)
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_event name, meth
|
45
|
+
self.context.add_event(name, self, meth)
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_events hash
|
49
|
+
hash.each do |event, meth|
|
50
|
+
add_event(event, meth)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module AutoRegister
|
55
|
+
EVENT_REGEX = /\Aon_(.+)\z/
|
56
|
+
|
57
|
+
def register
|
58
|
+
methods.map {|meth| meth.to_s }.grep(EVENT_REGEX).each do |meth|
|
59
|
+
meth =~ EVENT_REGEX
|
60
|
+
add_event $1.upcase, meth
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|