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.
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