classx-pluggable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|