bumbleworks 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +2 -0
  2. data/.rspec +3 -0
  3. data/.ruby-version +1 -0
  4. data/.watchr +89 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +84 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +160 -0
  9. data/Rakefile +9 -0
  10. data/bumbleworks.gemspec +30 -0
  11. data/doc/GUIDE.md +337 -0
  12. data/doc/TERMS.md +9 -0
  13. data/lib/bumbleworks.rb +123 -0
  14. data/lib/bumbleworks/configuration.rb +182 -0
  15. data/lib/bumbleworks/hash_storage.rb +13 -0
  16. data/lib/bumbleworks/participant_registration.rb +19 -0
  17. data/lib/bumbleworks/process_definition.rb +143 -0
  18. data/lib/bumbleworks/ruote.rb +64 -0
  19. data/lib/bumbleworks/storage_adapter.rb +23 -0
  20. data/lib/bumbleworks/support.rb +20 -0
  21. data/lib/bumbleworks/task.rb +109 -0
  22. data/lib/bumbleworks/tree_builder.rb +60 -0
  23. data/lib/bumbleworks/version.rb +3 -0
  24. data/spec/fixtures/apps/with_default_directories/app/participants/honey_participant.rb +3 -0
  25. data/spec/fixtures/apps/with_default_directories/app/participants/molasses_participant.rb +3 -0
  26. data/spec/fixtures/apps/with_default_directories/config_initializer.rb +4 -0
  27. data/spec/fixtures/apps/with_default_directories/full_initializer.rb +12 -0
  28. data/spec/fixtures/apps/with_default_directories/lib/process_definitions/garbage_collector.rb +3 -0
  29. data/spec/fixtures/apps/with_default_directories/lib/process_definitions/make_honey.rb +3 -0
  30. data/spec/fixtures/apps/with_default_directories/lib/process_definitions/make_molasses.rb +6 -0
  31. data/spec/fixtures/apps/with_specified_directories/config_initializer.rb +5 -0
  32. data/spec/fixtures/apps/with_specified_directories/specific_directory/definitions/.gitkeep +0 -0
  33. data/spec/fixtures/apps/with_specified_directories/specific_directory/participants/.gitkeep +0 -0
  34. data/spec/fixtures/definitions/a_list_of_jams.rb +4 -0
  35. data/spec/fixtures/definitions/nested_folder/test_nested_process.rb +3 -0
  36. data/spec/fixtures/definitions/test_process.rb +5 -0
  37. data/spec/integration/configuration_spec.rb +43 -0
  38. data/spec/integration/sample_application_spec.rb +45 -0
  39. data/spec/lib/bumbleworks/configuration_spec.rb +162 -0
  40. data/spec/lib/bumbleworks/participant_registration_spec.rb +13 -0
  41. data/spec/lib/bumbleworks/process_definition_spec.rb +178 -0
  42. data/spec/lib/bumbleworks/ruote_spec.rb +107 -0
  43. data/spec/lib/bumbleworks/storage_adapter_spec.rb +41 -0
  44. data/spec/lib/bumbleworks/support_spec.rb +40 -0
  45. data/spec/lib/bumbleworks/task_spec.rb +274 -0
  46. data/spec/lib/bumbleworks/tree_builder_spec.rb +95 -0
  47. data/spec/lib/bumbleworks_spec.rb +133 -0
  48. data/spec/spec_helper.rb +20 -0
  49. data/spec/support/path_helpers.rb +11 -0
  50. metadata +262 -0
@@ -0,0 +1,162 @@
1
+ describe Bumbleworks::Configuration do
2
+ let(:configuration) {described_class.new}
3
+ before :each do
4
+ configuration.clear!
5
+ end
6
+
7
+ describe "#root" do
8
+ it 'raises an error if client did not define' do
9
+ expect{configuration.root}.to raise_error Bumbleworks::UndefinedSetting
10
+ end
11
+
12
+ it 'returns folder set by user' do
13
+ configuration.root = '/what/about/that'
14
+ configuration.root.should == '/what/about/that'
15
+ end
16
+
17
+ it 'uses Rails.root if Rails is defined' do
18
+ class Rails
19
+ def self.root
20
+ '/Rails/Root'
21
+ end
22
+ end
23
+
24
+ configuration.root.should == '/Rails/Root'
25
+ Object.send(:remove_const, :Rails)
26
+ end
27
+
28
+ it 'uses Sinatra::Application.root if defined' do
29
+ class Sinatra
30
+ class Application
31
+ def self.root
32
+ '/Sinatra/Root'
33
+ end
34
+ end
35
+ end
36
+
37
+ configuration.root.should == '/Sinatra/Root'
38
+ Object.send(:remove_const, :Sinatra)
39
+ end
40
+
41
+ it 'uses Rory.root if defined' do
42
+ class Rory
43
+ def self.root
44
+ '/Rory/Root'
45
+ end
46
+ end
47
+
48
+ configuration.root.should == '/Rory/Root'
49
+ Object.send(:remove_const, :Rory)
50
+ end
51
+ end
52
+
53
+ describe "#definitions_directory" do
54
+ it 'returns the folder which was set by the client app' do
55
+ File.stub(:directory?).with('/dog/ate/my/homework').and_return(true)
56
+ configuration.definitions_directory = '/dog/ate/my/homework'
57
+ configuration.definitions_directory.should == '/dog/ate/my/homework'
58
+ end
59
+
60
+ it 'returns the default folder if not set by client app' do
61
+ File.stub(:directory? => true)
62
+ configuration.root = '/Root'
63
+ configuration.definitions_directory.should == '/Root/lib/process_definitions'
64
+ end
65
+
66
+ it 'raises an error if default folder not found' do
67
+ configuration.root = '/Root'
68
+ expect{configuration.definitions_directory}.to raise_error Bumbleworks::InvalidSetting
69
+ end
70
+
71
+ it 'raises an error if specific folder not found' do
72
+ configuration.definitions_directory = '/mumbo/jumbo'
73
+ expect{configuration.definitions_directory}.to raise_error Bumbleworks::InvalidSetting
74
+ end
75
+ end
76
+
77
+ describe "#participants_directory" do
78
+ it 'returns the folder which was set by the client app' do
79
+ File.stub(:directory?).with('/dog/ate/my/homework').and_return(true)
80
+ configuration.participants_directory = '/dog/ate/my/homework'
81
+ configuration.participants_directory.should == '/dog/ate/my/homework'
82
+ end
83
+
84
+ it 'returns the default folder if not set by client app' do
85
+ File.stub(:directory? => false)
86
+ File.stub(:directory?).with('/Root/app/participants').and_return(true)
87
+ configuration.root = '/Root'
88
+ configuration.participants_directory.should == '/Root/app/participants'
89
+ end
90
+
91
+ it 'raises an error if default folder not found' do
92
+ configuration.root = '/Root'
93
+ expect{configuration.participants_directory}.to raise_error Bumbleworks::InvalidSetting
94
+ end
95
+
96
+ it 'raises an error if specific folder not found' do
97
+ configuration.participants_directory = '/mumbo/jumbo'
98
+ expect{configuration.participants_directory}.to raise_error Bumbleworks::InvalidSetting
99
+ end
100
+ end
101
+
102
+ describe "#storage" do
103
+ it 'can set storage directly' do
104
+ storage = double("Storage")
105
+ configuration.storage = storage
106
+ configuration.storage.should == storage
107
+ end
108
+ end
109
+
110
+ describe '#add_storage_adapter' do
111
+ it 'adds storage adapter to registered list' do
112
+ GoodForNothingStorage = OpenStruct.new(
113
+ :driver => nil, :display_name => 'Dummy', :use? => true
114
+ )
115
+ configuration.storage_adapters.should be_empty
116
+ configuration.add_storage_adapter(GoodForNothingStorage)
117
+ configuration.add_storage_adapter(Bumbleworks::HashStorage)
118
+ configuration.storage_adapters.should =~ [
119
+ GoodForNothingStorage, Bumbleworks::HashStorage
120
+ ]
121
+ end
122
+
123
+ it 'raises ArgumentError if object is not a storage adapter' do
124
+ expect {
125
+ configuration.add_storage_adapter(:nice_try_buddy)
126
+ }.to raise_error(ArgumentError)
127
+ end
128
+ end
129
+
130
+ describe '#autostart_worker' do
131
+ it 'returns false by default' do
132
+ configuration.autostart_worker.should be_false
133
+ end
134
+
135
+ it 'only returns true if set explicitly to true' do
136
+ configuration.autostart_worker = 'yes'
137
+ configuration.autostart_worker.should be_false
138
+ configuration.autostart_worker = 1
139
+ configuration.autostart_worker.should be_false
140
+ configuration.autostart_worker = true
141
+ configuration.autostart_worker.should be_true
142
+ end
143
+ end
144
+
145
+ describe '#clear!' do
146
+ it 'resets #root' do
147
+ configuration.root = '/Root'
148
+ configuration.clear!
149
+ expect{configuration.root}.to raise_error Bumbleworks::UndefinedSetting
150
+ end
151
+
152
+ it 'resets #definitions_directory' do
153
+ File.stub(:directory? => true)
154
+ configuration.definitions_directory = '/One/Two'
155
+ configuration.definitions_directory.should == '/One/Two'
156
+ configuration.clear!
157
+
158
+ configuration.root = '/Root'
159
+ configuration.definitions_directory.should == '/Root/lib/process_definitions'
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,13 @@
1
+ describe Bumbleworks::ParticipantRegistration do
2
+ describe '.autoload_all' do
3
+ it 'autoloads all participants in directory' do
4
+ Bumbleworks.reset!
5
+ Bumbleworks.root = File.join(fixtures_path, 'apps', 'with_default_directories')
6
+ Object.should_receive(:autoload).with(:HoneyParticipant,
7
+ File.join(Bumbleworks.root, 'app', 'participants', 'honey_participant.rb'))
8
+ Object.should_receive(:autoload).with(:MolassesParticipant,
9
+ File.join(Bumbleworks.root, 'app', 'participants', 'molasses_participant.rb'))
10
+ Bumbleworks::ParticipantRegistration.autoload_all
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,178 @@
1
+ describe Bumbleworks::ProcessDefinition do
2
+ before :each do
3
+ Bumbleworks.reset!
4
+ Bumbleworks.storage = {}
5
+ end
6
+
7
+ let(:valid_definition) { %q(
8
+ Bumbleworks.define_process 'monkeys' do
9
+ chocolate_covered_skis :are => 'awesome'
10
+ end
11
+ )}
12
+
13
+ it "can be constructed with name and definition" do
14
+ pdef = described_class.new(:name => 'zach', :definition => 'orbo')
15
+ pdef.name.should == 'zach'
16
+ pdef.definition.should == 'orbo'
17
+ end
18
+
19
+ it "can be constructed with name and tree" do
20
+ pdef = described_class.new(:name => 'zach', :tree => ["define", {"your" => "face"}])
21
+ pdef.name.should == 'zach'
22
+ pdef.tree.should == ["define", {"your" => "face"}]
23
+ end
24
+
25
+ describe '#build_tree!' do
26
+ it "converts Bumbleworks definition to Ruote tree" do
27
+ pdef = described_class.new(:name => 'monkeys', :definition => valid_definition)
28
+ pdef.build_tree!.should == [
29
+ "define", {"name" => "monkeys"},
30
+ [
31
+ ["chocolate_covered_skis", {"are" => "awesome"}, []]
32
+ ]
33
+ ]
34
+ end
35
+
36
+ it "raises error if name in definition does not match" do
37
+ wrong_named_def = valid_definition.gsub(/monkeys/, 'slot_cats')
38
+ pdef = described_class.new(:name => 'monkeys', :definition => wrong_named_def)
39
+ expect {
40
+ pdef.build_tree!
41
+ }.to raise_error(described_class::Invalid, /Name does not match name in definition/)
42
+ end
43
+
44
+ it "adds name to tree when not specified in definition" do
45
+ unnamed_def = valid_definition.gsub(/ 'monkeys'/, '')
46
+ pdef = described_class.new(:name => 'spit_salad', :definition => unnamed_def)
47
+ pdef.build_tree!.should == [
48
+ "define", {"name" => "spit_salad"},
49
+ [
50
+ ["chocolate_covered_skis", {"are" => "awesome"}, []]
51
+ ]
52
+ ]
53
+ end
54
+ end
55
+
56
+ describe "#validate!" do
57
+ it "raises error if name not specified" do
58
+ expect {
59
+ described_class.new.validate!
60
+ }.to raise_error(described_class::Invalid, /Name must be specified/)
61
+ end
62
+
63
+ it "raises error if definition and tree not specified" do
64
+ expect {
65
+ described_class.new.validate!
66
+ }.to raise_error(described_class::Invalid, /Definition or tree must be specified/)
67
+ end
68
+
69
+ it "raises error if definition invalid" do
70
+ pdef = described_class.new(:name => 'yay', :definition => 'do good stuff')
71
+ expect {
72
+ pdef.validate!
73
+ }.to raise_error(described_class::Invalid, /Definition is not a valid process definition/)
74
+ end
75
+
76
+ it "raises error if name already taken" do
77
+ Bumbleworks.dashboard.variables['tasty_beans'] = 'A miraculous ingredient'
78
+ expect {
79
+ described_class.new(:name => 'tasty_beans').validate!
80
+ }.to raise_error(described_class::Invalid, /Name is not unique/)
81
+ end
82
+ end
83
+
84
+ describe "#save!" do
85
+ it "validates" do
86
+ pdef = described_class.new
87
+ pdef.should_receive(:validate!)
88
+ pdef.save!
89
+ end
90
+
91
+ it "raises validation error if invalid" do
92
+ expect {
93
+ described_class.new.save!
94
+ }.to raise_error(described_class::Invalid)
95
+ end
96
+
97
+ it "registers the process definition with the dashboard" do
98
+ pdef = described_class.new(:name => 'monkeys', :definition => valid_definition)
99
+ pdef.save!
100
+ Bumbleworks.dashboard.variables['monkeys'].should ==
101
+ pdef.build_tree!
102
+ end
103
+ end
104
+
105
+ describe ".find_by_name" do
106
+ it "returns an instance with a previously registered definition" do
107
+ Bumbleworks.dashboard.variables['foo'] = 'go to the bar'
108
+ pdef = described_class.find_by_name('foo')
109
+ pdef.tree.should == 'go to the bar'
110
+ end
111
+
112
+ it "raises an error if registered definition can't be found by given name" do
113
+ lambda { described_class.find_by_name('nerf') }.should raise_error(described_class::NotFound)
114
+ end
115
+ end
116
+
117
+ describe "#load_definition_from_file" do
118
+ it "raises an error if given file can't be found" do
119
+ pdef = described_class.new(:name => 'whatever')
120
+ lambda { pdef.load_definition_from_file('nerf') }.should raise_error(described_class::FileNotFound)
121
+ end
122
+
123
+ it "sets #definition to the parsed result of the file, if found" do
124
+ pdef = described_class.new(:name => 'test_process')
125
+ pdef.load_definition_from_file definition_path('test_process')
126
+ pdef.definition.should == File.read(definition_path('test_process'))
127
+ end
128
+ end
129
+
130
+ describe '.create_all_from_directory!' do
131
+ let(:definitions_path) { File.join(fixtures_path, 'definitions') }
132
+
133
+ it 'raises error if any invalid files are encountered' do
134
+ expect {
135
+ described_class.create_all_from_directory!(definitions_path)
136
+ }.to raise_error(described_class::Invalid)
137
+ end
138
+
139
+ it 'rolls back any processes defined within current transaction if error' do
140
+ Bumbleworks.dashboard.variables['keep_me'] = 'top_secret_cat_pics'
141
+ Bumbleworks::Support.stub(:all_files).
142
+ and_yield(definition_path('test_process'), 'test_process').
143
+ and_yield(definition_path('a_list_of_jams'), 'a_list_of_jams')
144
+ expect {
145
+ described_class.create_all_from_directory!(definitions_path)
146
+ }.to raise_error
147
+ Bumbleworks.dashboard.variables['test_process'].should be_nil
148
+ Bumbleworks.dashboard.variables['keep_me'].should == 'top_secret_cat_pics'
149
+ end
150
+
151
+ it 'skips invalid files and loads all valid definitions when option specified' do
152
+ described_class.create_all_from_directory!(definitions_path, :skip_invalid => true)
153
+ Bumbleworks.dashboard.variables['test_process'].should ==
154
+ ["define", { "name" => "test_process" }, [["nothing", {}, []]]]
155
+ Bumbleworks.dashboard.variables['test_nested_process'].should ==
156
+ ["define", { "name" => "test_nested_process" }, [["nothing_nested", {}, []]]]
157
+ Bumbleworks.dashboard.variables['a_list_of_jams'].should be_nil
158
+ end
159
+ end
160
+
161
+ describe '.create!' do
162
+ it 'builds and saves a new instance' do
163
+ pdef = described_class.create!(:name => 'monkeys', :definition => valid_definition)
164
+ Bumbleworks.dashboard.variables['monkeys'].should ==
165
+ pdef.build_tree!
166
+ end
167
+ end
168
+
169
+ describe '.define' do
170
+ it 'creates a process definition from a given block' do
171
+ definition = described_class.define 'nammikins' do
172
+ treefles
173
+ end
174
+ Bumbleworks.dashboard.variables['nammikins'].should ==
175
+ ["define", {"name" => "nammikins"}, [["treefles", {}, []]]]
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,107 @@
1
+ describe Bumbleworks::Ruote do
2
+ before :each do
3
+ Bumbleworks.reset!
4
+ end
5
+
6
+ describe '.dashboard' do
7
+ it 'raises an error if no storage is defined' do
8
+ Bumbleworks.storage = nil
9
+ expect { described_class.dashboard }.to raise_error Bumbleworks::UndefinedSetting
10
+ end
11
+
12
+ it 'creates a new dashboard' do
13
+ Bumbleworks.storage = {}
14
+ described_class.dashboard.should be_an_instance_of(Ruote::Dashboard)
15
+ end
16
+
17
+ it 'does not start a worker if autostart is false' do
18
+ Bumbleworks.storage = {}
19
+ described_class.dashboard.worker.should be_nil
20
+ end
21
+
22
+ it 'starts a worker if autostart is true' do
23
+ Bumbleworks.storage = {}
24
+ Bumbleworks.autostart_worker = true
25
+ described_class.dashboard.worker.should_not be_nil
26
+ end
27
+
28
+ it 'starts a worker if :start_worker option is true' do
29
+ Bumbleworks.storage = {}
30
+ described_class.dashboard(:start_worker => true).worker.should_not be_nil
31
+ end
32
+ end
33
+
34
+ describe '.start_worker!' do
35
+ it 'adds new worker to dashboard and returns worker' do
36
+ Bumbleworks.storage = {}
37
+ described_class.dashboard.worker.should be_nil
38
+ new_worker = described_class.start_worker!
39
+ new_worker.should be_an_instance_of(Ruote::Worker)
40
+ described_class.dashboard.worker.should == new_worker
41
+ end
42
+
43
+ it 'joins current thread if :join option is true' do
44
+ Bumbleworks.storage = {}
45
+ ::Ruote::Dashboard.stub(:new).and_return(dash_double = double('dash', :worker => nil))
46
+ dash_double.should_receive(:join)
47
+ described_class.start_worker!(:join => true)
48
+ end
49
+
50
+ it 'returns if :join option not true' do
51
+ Bumbleworks.storage = {}
52
+ ::Ruote::Dashboard.stub(:new).and_return(dash_double = double('dash', :worker => nil))
53
+ dash_double.should_receive(:join).never
54
+ described_class.start_worker!
55
+ end
56
+ end
57
+
58
+ describe '.register_participants' do
59
+ it 'loads participants from given block, adding storage participant catchall' do
60
+ registration_block = Proc.new {
61
+ bees_honey 'BeesHoney'
62
+ maple_syrup 'MapleSyrup'
63
+ catchall 'NewCatchall'
64
+ }
65
+
66
+ Bumbleworks.storage = {}
67
+ described_class.dashboard.participant_list.should be_empty
68
+ described_class.register_participants &registration_block
69
+ described_class.dashboard.participant_list.should have(4).items
70
+ described_class.dashboard.participant_list.map(&:classname).should =~ ['BeesHoney', 'MapleSyrup', 'NewCatchall', 'Ruote::StorageParticipant']
71
+ end
72
+
73
+ it 'does not add storage participant catchall if already exists' do
74
+ registration_block = Proc.new {
75
+ bees_honey 'BeesHoney'
76
+ catchall
77
+ }
78
+
79
+ Bumbleworks.storage = {}
80
+ described_class.dashboard.participant_list.should be_empty
81
+ described_class.register_participants &registration_block
82
+ described_class.dashboard.participant_list.should have(2).items
83
+ described_class.dashboard.participant_list.map(&:classname).should =~ ['BeesHoney', 'Ruote::StorageParticipant']
84
+ end
85
+
86
+ it 'adds catchall participant if block is nil' do
87
+ Bumbleworks.storage = {}
88
+ described_class.dashboard.participant_list.should be_empty
89
+ described_class.register_participants &nil
90
+ described_class.dashboard.participant_list.should have(1).item
91
+ described_class.dashboard.participant_list.first.classname.should == 'Ruote::StorageParticipant'
92
+ end
93
+ end
94
+
95
+ describe '.storage' do
96
+ it 'raise error when storage is not defined' do
97
+ expect { described_class.send(:storage) }.to raise_error Bumbleworks::UndefinedSetting
98
+ end
99
+
100
+ it 'handles Hash storage' do
101
+ storage = {}
102
+ Bumbleworks.storage = storage
103
+ Ruote::HashStorage.should_receive(:new).with(storage)
104
+ described_class.send(:storage)
105
+ end
106
+ end
107
+ end