bumbleworks 0.0.4
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/.gitignore +2 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.watchr +89 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +84 -0
- data/LICENSE.txt +22 -0
- data/README.md +160 -0
- data/Rakefile +9 -0
- data/bumbleworks.gemspec +30 -0
- data/doc/GUIDE.md +337 -0
- data/doc/TERMS.md +9 -0
- data/lib/bumbleworks.rb +123 -0
- data/lib/bumbleworks/configuration.rb +182 -0
- data/lib/bumbleworks/hash_storage.rb +13 -0
- data/lib/bumbleworks/participant_registration.rb +19 -0
- data/lib/bumbleworks/process_definition.rb +143 -0
- data/lib/bumbleworks/ruote.rb +64 -0
- data/lib/bumbleworks/storage_adapter.rb +23 -0
- data/lib/bumbleworks/support.rb +20 -0
- data/lib/bumbleworks/task.rb +109 -0
- data/lib/bumbleworks/tree_builder.rb +60 -0
- data/lib/bumbleworks/version.rb +3 -0
- data/spec/fixtures/apps/with_default_directories/app/participants/honey_participant.rb +3 -0
- data/spec/fixtures/apps/with_default_directories/app/participants/molasses_participant.rb +3 -0
- data/spec/fixtures/apps/with_default_directories/config_initializer.rb +4 -0
- data/spec/fixtures/apps/with_default_directories/full_initializer.rb +12 -0
- data/spec/fixtures/apps/with_default_directories/lib/process_definitions/garbage_collector.rb +3 -0
- data/spec/fixtures/apps/with_default_directories/lib/process_definitions/make_honey.rb +3 -0
- data/spec/fixtures/apps/with_default_directories/lib/process_definitions/make_molasses.rb +6 -0
- data/spec/fixtures/apps/with_specified_directories/config_initializer.rb +5 -0
- data/spec/fixtures/apps/with_specified_directories/specific_directory/definitions/.gitkeep +0 -0
- data/spec/fixtures/apps/with_specified_directories/specific_directory/participants/.gitkeep +0 -0
- data/spec/fixtures/definitions/a_list_of_jams.rb +4 -0
- data/spec/fixtures/definitions/nested_folder/test_nested_process.rb +3 -0
- data/spec/fixtures/definitions/test_process.rb +5 -0
- data/spec/integration/configuration_spec.rb +43 -0
- data/spec/integration/sample_application_spec.rb +45 -0
- data/spec/lib/bumbleworks/configuration_spec.rb +162 -0
- data/spec/lib/bumbleworks/participant_registration_spec.rb +13 -0
- data/spec/lib/bumbleworks/process_definition_spec.rb +178 -0
- data/spec/lib/bumbleworks/ruote_spec.rb +107 -0
- data/spec/lib/bumbleworks/storage_adapter_spec.rb +41 -0
- data/spec/lib/bumbleworks/support_spec.rb +40 -0
- data/spec/lib/bumbleworks/task_spec.rb +274 -0
- data/spec/lib/bumbleworks/tree_builder_spec.rb +95 -0
- data/spec/lib/bumbleworks_spec.rb +133 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/path_helpers.rb +11 -0
- metadata +262 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
describe Bumbleworks::StorageAdapter do
|
2
|
+
describe '.auto_register?' do
|
3
|
+
it 'returns true if auto_register is true' do
|
4
|
+
described_class.auto_register = true
|
5
|
+
described_class.auto_register?.should be_true
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'returns false if auto_register is not true' do
|
9
|
+
described_class.auto_register = :ghosts
|
10
|
+
described_class.auto_register?.should be_false
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'is true by default' do
|
14
|
+
described_class.auto_register = nil
|
15
|
+
described_class.auto_register?.should be_true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.display_name' do
|
20
|
+
it 'is a subclass responsibility' do
|
21
|
+
expect { described_class.display_name }.to raise_error
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '.driver' do
|
26
|
+
it 'is a subclass responsibility' do
|
27
|
+
expect { described_class.driver }.to raise_error
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '.use?' do
|
32
|
+
before :each do
|
33
|
+
described_class.stub(:display_name).and_return('String')
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns true if argument class name matches display name' do
|
37
|
+
described_class.use?('a string').should be_true
|
38
|
+
described_class.use?(:not_a_string).should be_false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
describe Bumbleworks::Support do
|
2
|
+
describe '.camelize' do
|
3
|
+
it 'turns underscored string into camelcase' do
|
4
|
+
described_class.camelize('foo_bar_One_two_3').should == 'FooBarOneTwo3'
|
5
|
+
end
|
6
|
+
|
7
|
+
it 'deals with nested classes' do
|
8
|
+
described_class.camelize('foo_bar/bar_foo').should == 'FooBar::BarFoo'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe ".all_files" do
|
13
|
+
let(:test_directory) { File.join(fixtures_path, 'definitions').to_s }
|
14
|
+
|
15
|
+
it "for given directory, yields given block with path and name params" do
|
16
|
+
assembled_hash = {}
|
17
|
+
described_class.all_files(test_directory) do |path, name|
|
18
|
+
assembled_hash[name] = path
|
19
|
+
end
|
20
|
+
|
21
|
+
assembled_hash['test_process'].should ==
|
22
|
+
File.join(fixtures_path, 'definitions', 'test_process.rb').to_s
|
23
|
+
assembled_hash['test_nested_process'].should ==
|
24
|
+
File.join(fixtures_path, 'definitions', 'nested_folder', 'test_nested_process.rb').to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
it "camelizes names if :camelize option is true " do
|
28
|
+
path = File.join(fixtures_path, 'definitions')
|
29
|
+
assembled_hash = {}
|
30
|
+
described_class.all_files(test_directory, :camelize => true) do |path, name|
|
31
|
+
assembled_hash[name] = path
|
32
|
+
end
|
33
|
+
|
34
|
+
assembled_hash['TestProcess'].should ==
|
35
|
+
File.join(fixtures_path, 'definitions', 'test_process.rb').to_s
|
36
|
+
assembled_hash['TestNestedProcess'].should ==
|
37
|
+
File.join(fixtures_path, 'definitions', 'nested_folder', 'test_nested_process.rb').to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
describe Bumbleworks::Task do
|
2
|
+
let(:workflow_item) {Ruote::Workitem.new('fields' => {'params' => {'task' => 'go_to_work'} })}
|
3
|
+
|
4
|
+
before :each do
|
5
|
+
Bumbleworks.reset!
|
6
|
+
Bumbleworks.autostart_worker = true
|
7
|
+
Bumbleworks.storage = {}
|
8
|
+
Bumbleworks::Ruote.register_participants
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '.new' do
|
12
|
+
it 'raises an error if workitem is nil' do
|
13
|
+
expect {
|
14
|
+
described_class.new(nil)
|
15
|
+
}.to raise_error(ArgumentError, "Not a valid workitem")
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'raises an error if workitem not a Ruote::Workitem' do
|
19
|
+
expect {
|
20
|
+
described_class.new('a string!')
|
21
|
+
}.to raise_error(ArgumentError, "Not a valid workitem")
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'succeeds when given workitem' do
|
25
|
+
expect {
|
26
|
+
described_class.new(workflow_item)
|
27
|
+
}.not_to raise_error
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#id' do
|
32
|
+
it 'returns the sid from the workitem' do
|
33
|
+
workflow_item.stub(:sid).and_return(:an_exciting_id)
|
34
|
+
described_class.new(workflow_item).id.should == :an_exciting_id
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.find_by_id' do
|
39
|
+
it 'returns the task for the given id' do
|
40
|
+
Bumbleworks.define_process 'planting_a_noodle' do
|
41
|
+
concurrence do
|
42
|
+
noodle_gardener :task => 'plant_noodle_seed'
|
43
|
+
horse_feeder :task => 'give_the_horse_a_bon_bon'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
Bumbleworks.launch!('planting_a_noodle')
|
47
|
+
Bumbleworks.dashboard.wait_for(:horse_feeder)
|
48
|
+
plant_noodle_seed_task = described_class.for_role('noodle_gardener').first
|
49
|
+
give_the_horse_a_bon_bon_task = described_class.for_role('horse_feeder').first
|
50
|
+
|
51
|
+
# checking for equality by comparing sid, which is the flow expression id
|
52
|
+
# that identifies not only the expression, but its instance
|
53
|
+
described_class.find_by_id(plant_noodle_seed_task.id).sid.should ==
|
54
|
+
plant_noodle_seed_task.sid
|
55
|
+
described_class.find_by_id(give_the_horse_a_bon_bon_task.id).sid.should ==
|
56
|
+
give_the_horse_a_bon_bon_task.sid
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'raises an error if id is nil' do
|
60
|
+
expect {
|
61
|
+
described_class.find_by_id(nil)
|
62
|
+
}.to raise_error(described_class::MissingWorkitem)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'raises an error if workitem not found for given id' do
|
66
|
+
expect {
|
67
|
+
described_class.find_by_id('asdfasdf')
|
68
|
+
}.to raise_error(described_class::MissingWorkitem)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'raises an error if id is unparseable by storage participant' do
|
72
|
+
expect {
|
73
|
+
described_class.find_by_id(:unparseable_because_i_am_a_symbol)
|
74
|
+
}.to raise_error(described_class::MissingWorkitem)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '.for_roles' do
|
79
|
+
before :each do
|
80
|
+
Bumbleworks.define_process 'lowering_penguin_self_esteem' do
|
81
|
+
concurrence do
|
82
|
+
heckler :task => 'comment_on_dancing_ability'
|
83
|
+
mother :oh_no => 'this_is_not_a_task'
|
84
|
+
mother :task => 'ignore_pleas_for_attention'
|
85
|
+
father :task => 'sit_around_watching_penguin_tv'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
Bumbleworks.launch!('lowering_penguin_self_esteem')
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'returns tasks for all given roles' do
|
92
|
+
Bumbleworks.dashboard.wait_for(:father)
|
93
|
+
tasks = described_class.for_roles(['heckler', 'mother'])
|
94
|
+
tasks.should have(2).items
|
95
|
+
tasks.map(&:nickname).should == [
|
96
|
+
'comment_on_dancing_ability',
|
97
|
+
'ignore_pleas_for_attention'
|
98
|
+
]
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'returns empty array if no tasks found for given roles' do
|
102
|
+
Bumbleworks.dashboard.wait_for(:father)
|
103
|
+
described_class.for_roles(['elephant']).should be_empty
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns empty array if given empty array' do
|
107
|
+
Bumbleworks.dashboard.wait_for(:father)
|
108
|
+
described_class.for_roles([]).should be_empty
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns empty array if given nil' do
|
112
|
+
Bumbleworks.dashboard.wait_for(:father)
|
113
|
+
described_class.for_roles(nil).should be_empty
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '.for_role' do
|
118
|
+
it 'delegates to #for_roles with single-item array' do
|
119
|
+
described_class.should_receive(:for_roles).with(['mister_mystery'])
|
120
|
+
described_class.for_role('mister_mystery')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '.all' do
|
125
|
+
before :each do
|
126
|
+
Bumbleworks.define_process 'dog-lifecycle' do
|
127
|
+
concurrence do
|
128
|
+
dog_teeth :task => 'eat'
|
129
|
+
dog_mouth :task => 'bark'
|
130
|
+
everyone :task => 'pet_dog'
|
131
|
+
the_universe_is_wonderful
|
132
|
+
dog_legs :task => 'skip_and_jump'
|
133
|
+
end
|
134
|
+
dog_brain :task => 'nap'
|
135
|
+
end
|
136
|
+
Bumbleworks.launch!('dog-lifecycle')
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'returns all tasks (with task param) in queue regardless of role' do
|
140
|
+
Bumbleworks.dashboard.wait_for(:dog_legs)
|
141
|
+
tasks = described_class.all
|
142
|
+
tasks.should have(4).items
|
143
|
+
tasks.map { |t| [t.role, t.nickname] }.should == [
|
144
|
+
['dog_teeth', 'eat'],
|
145
|
+
['dog_mouth', 'bark'],
|
146
|
+
['everyone', 'pet_dog'],
|
147
|
+
['dog_legs', 'skip_and_jump']
|
148
|
+
]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe '#[], #[]=' do
|
153
|
+
subject{described_class.new(workflow_item)}
|
154
|
+
it 'sets values on workitem fields' do
|
155
|
+
subject['hive'] = 'bees at work'
|
156
|
+
workflow_item.fields['hive'].should == 'bees at work'
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'retuns value from workitem params' do
|
160
|
+
workflow_item.fields['nest'] = 'queen resting'
|
161
|
+
subject['nest'].should == 'queen resting'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe '#nickname' do
|
166
|
+
it 'returns the "task" param' do
|
167
|
+
described_class.new(workflow_item).nickname.should == 'go_to_work'
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'is immutable; cannot be changed by modified the param' do
|
171
|
+
task = described_class.new(workflow_item)
|
172
|
+
task.nickname.should == 'go_to_work'
|
173
|
+
task.params['task'] = 'what_is_wrong_with_you?'
|
174
|
+
task.nickname.should == 'go_to_work'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe '#role' do
|
179
|
+
it 'returns the workitem participant_name' do
|
180
|
+
Bumbleworks.define_process 'planting_a_noodle' do
|
181
|
+
noodle_gardener :task => 'plant_noodle_seed'
|
182
|
+
end
|
183
|
+
Bumbleworks.launch!('planting_a_noodle')
|
184
|
+
Bumbleworks.dashboard.wait_for(:noodle_gardener)
|
185
|
+
described_class.all.first.role.should == 'noodle_gardener'
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'claiming things' do
|
190
|
+
subject{described_class.new(workflow_item)}
|
191
|
+
before :each do
|
192
|
+
subject.stub(:save)
|
193
|
+
workflow_item.params['claimant'] = nil
|
194
|
+
subject.claim('boss')
|
195
|
+
end
|
196
|
+
|
197
|
+
describe '#claim' do
|
198
|
+
it 'sets token on "claimant" param' do
|
199
|
+
workflow_item.params['claimant'].should == 'boss'
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'raises an error if already claimed by someone else' do
|
203
|
+
expect{subject.claim('peon')}.to raise_error described_class::AlreadyClaimed
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'does not raise an error if attempting to claim by same token' do
|
207
|
+
expect{subject.claim('boss')}.not_to raise_error described_class::AlreadyClaimed
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
describe '#claimant' do
|
212
|
+
it 'returns token of who has claim' do
|
213
|
+
subject.claimant.should == 'boss'
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
describe '#claimed?' do
|
218
|
+
it 'returns true if claimed' do
|
219
|
+
subject.claimed?.should be_true
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'false otherwise' do
|
223
|
+
workflow_item.params['claimant'] = nil
|
224
|
+
subject.claimed?.should be_false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '#release' do
|
229
|
+
it "release claim on workitem" do
|
230
|
+
subject.should be_claimed
|
231
|
+
subject.release
|
232
|
+
subject.should_not be_claimed
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context 'updating workflow engine' do
|
238
|
+
before :each do
|
239
|
+
Bumbleworks.define_process 'dog-lifecycle' do
|
240
|
+
dog_mouth :task => 'eat_dinner', :state => 'still cooking'
|
241
|
+
dog_brain :task => 'cat_nap', :by => 'midnight'
|
242
|
+
end
|
243
|
+
Bumbleworks.launch!('dog-lifecycle')
|
244
|
+
end
|
245
|
+
|
246
|
+
describe '#save' do
|
247
|
+
it 'saves fields and params, but does not proceed process' do
|
248
|
+
event = Bumbleworks.dashboard.wait_for :dog_mouth
|
249
|
+
task = described_class.for_role('dog_mouth').first
|
250
|
+
task.params['state'] = 'is ready'
|
251
|
+
task.fields['meal'] = 'salted_rhubarb'
|
252
|
+
task.save
|
253
|
+
task = described_class.for_role('dog_mouth').first
|
254
|
+
task.params['state'].should == 'is ready'
|
255
|
+
task.fields['meal'].should == 'salted_rhubarb'
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe '#complete' do
|
260
|
+
it 'saves fields and proceeds to next expression' do
|
261
|
+
event = Bumbleworks.dashboard.wait_for :dog_mouth
|
262
|
+
task = described_class.for_role('dog_mouth').first
|
263
|
+
task.params['state'] = 'is ready'
|
264
|
+
task.fields['meal'] = 'root beer and a kite'
|
265
|
+
task.complete
|
266
|
+
described_class.for_role('dog_mouth').should be_empty
|
267
|
+
event = Bumbleworks.dashboard.wait_for :dog_brain
|
268
|
+
task = described_class.for_role('dog_brain').first
|
269
|
+
task.params['state'].should be_nil
|
270
|
+
task.fields['meal'].should == 'root beer and a kite'
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
describe Bumbleworks::TreeBuilder do
|
2
|
+
describe '.new' do
|
3
|
+
it 'raises error if no definition or tree' do
|
4
|
+
expect { described_class.new }.to raise_error(ArgumentError)
|
5
|
+
end
|
6
|
+
|
7
|
+
it 'raises error if both definition and tree' do
|
8
|
+
expect {
|
9
|
+
described_class.new(:definition => :foo, :tree => :bar)
|
10
|
+
}.to raise_error(ArgumentError)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'succeeds if only one of definition or tree specified' do
|
14
|
+
expect { described_class.new(:definition => :foo) }.not_to raise_error
|
15
|
+
expect { described_class.new(:tree => :foo) }.not_to raise_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#build!' do
|
20
|
+
it 'builds tree and sets name to given name' do
|
21
|
+
pdef = %q(
|
22
|
+
Bumbleworks.define_process do
|
23
|
+
order_pants_around
|
24
|
+
end
|
25
|
+
)
|
26
|
+
builder = described_class.new(:name => 'luigi', :definition => pdef)
|
27
|
+
builder.build!.should == [
|
28
|
+
"define", {"name" => "luigi"},
|
29
|
+
[
|
30
|
+
["order_pants_around", {}, []]
|
31
|
+
]
|
32
|
+
]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'uses tree if given' do
|
36
|
+
tree = ["define", {"guppies" => nil}, [["swim", {}, []]]]
|
37
|
+
builder = described_class.new(:tree => tree)
|
38
|
+
builder.build!.should ==
|
39
|
+
["define", {"name" => "guppies"}, [["swim", {}, []]]]
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'normalizes and sets name from tree' do
|
43
|
+
pdef = %q(
|
44
|
+
Bumbleworks.define_process 'country_time_county_dime' do
|
45
|
+
do_the_fig_newton
|
46
|
+
end
|
47
|
+
)
|
48
|
+
builder = described_class.new(:definition => pdef)
|
49
|
+
builder.build!.should == [
|
50
|
+
"define", {"name" => "country_time_county_dime"},
|
51
|
+
[
|
52
|
+
["do_the_fig_newton", {}, []]
|
53
|
+
]
|
54
|
+
]
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'raises error if name conflicts with name in definition' do
|
58
|
+
pdef = %q(
|
59
|
+
Bumbleworks.define_process :name => 'stromboli' do
|
60
|
+
order_pants_around
|
61
|
+
end
|
62
|
+
)
|
63
|
+
builder = described_class.new(:name => 'contorti', :definition => pdef)
|
64
|
+
expect {
|
65
|
+
builder.build!
|
66
|
+
}.to raise_error(described_class::InvalidTree, "Name does not match name in definition" )
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'raises error if process is unparseable' do
|
70
|
+
pdef = "A quiet evening with the deaf lemurs"
|
71
|
+
builder = described_class.new(:name => 'lemur_joy', :definition => pdef)
|
72
|
+
expect {
|
73
|
+
builder.build!
|
74
|
+
}.to raise_error(described_class::InvalidTree, "cannot read process definition" )
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '.from_definition' do
|
79
|
+
it 'instantiates a TreeBuilder from a given block' do
|
80
|
+
builder = described_class.from_definition 'just_another_poodle_day' do
|
81
|
+
chew :on => 'dad'
|
82
|
+
construct :solution => 'to world conflict'
|
83
|
+
end
|
84
|
+
builder.build!
|
85
|
+
builder.tree.should ==
|
86
|
+
["define", { "name" => "just_another_poodle_day" },
|
87
|
+
[
|
88
|
+
["chew", {"on" => "dad"}, []],
|
89
|
+
["construct", {"solution" => "to world conflict"}, []]
|
90
|
+
]
|
91
|
+
]
|
92
|
+
builder.name.should == 'just_another_poodle_day'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|