newbamboo-proto_processor 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ismael Celis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,150 @@
1
+ ## proto_processor
2
+
3
+ A couple of modules to ease the creation of background processors or any kind of delegated task or tasks for which you want flow control and error reporting.
4
+
5
+ ## Install
6
+
7
+ sudo gem install ismasan-proto_processor
8
+
9
+ ## Examples
10
+
11
+ ### Strategies
12
+
13
+ You start by defining a *strategy*. A strategy defines the sequence of tasks your program will carry out.
14
+
15
+ require 'rubygems'
16
+ require 'proto_processor'
17
+
18
+ class ImageStrategy
19
+ include ProtoProcessor::Strategy
20
+
21
+ # :process runs you strategy
22
+ #
23
+ def process
24
+
25
+ options = {:width => 300, :height => 200}
26
+
27
+ # set the initial input
28
+ with_input File.open('some_file.jpg')
29
+
30
+ # Run the file though a resize task
31
+ run_task ResizeTask, options
32
+
33
+ # store the modified file
34
+ run_task StoreFileTask
35
+ end
36
+
37
+ end
38
+
39
+ Run it:
40
+
41
+ strategy = ImageStrategy.new
42
+ report = strategy.run
43
+ puts report.inspect
44
+
45
+ The Strategy module just adds a few methods to run your tasks in a declarative manner. The :process method sets an input to be used as a starting point with *with_input*, and declares the sequence of tasks to be run on that input with *run_task*, which takes the task class and an options hash as arguments.
46
+
47
+ Strategy#run captures the output of tasks and gives you a Report object, with information about the tasks run, their output and possible errors.
48
+
49
+ Apart from the *process* method, Strategies are normal Ruby classes so you can go ahead and add whatever functionality you want to them, including, for example, an *initialize* method, or a factory.
50
+
51
+ ### Tasks
52
+
53
+ Tasks do the real work. They also implement a *process* method where you can put you image resizing, file storing, email sending or whatever code you want.
54
+
55
+ require 'mini_magick'
56
+ class ResizeTask
57
+ include ProtoProcessor::Task
58
+
59
+ expects_options_with :width
60
+ expects_options_with :height
61
+
62
+ def process
63
+ image = MiniMagick::Image.new(input.path)
64
+ image.resize "#{options[:width]}x#{options[:height]}"
65
+ end
66
+ end
67
+
68
+ That's pretty much it. Any exceptions raised within *process* will be captured, logged and stored in the task report which will be available as part of the Strategy-wide Report object.
69
+
70
+ Every task has an *options* hash available.
71
+
72
+ ### Validating task options
73
+
74
+ The previous example shows a simple way of checking that a task was passed required parameters as part of the options hash.
75
+
76
+ expects_options_with :width
77
+
78
+ If parameters declared in this way are not present in the options, a ProtoProcessor::MissingParametersError exception will be raised and logged in the task report (your task won't blow up though). The process method won't be run.
79
+
80
+ You can also raise manually in the process method.
81
+
82
+ def process
83
+ raise ArgumentError, ":width option must be > 0" if options[:width] < 1
84
+ end
85
+
86
+ Tasks also have *before_process* and *after_process* callbacks that will run if defined. You can guess what they do :)
87
+
88
+ Lastly, you can define your own *validate* method which will be run before processing. If validate returns false, the task won't process and I ProtoProcessor::TaskInvalidError will be logged in the error report
89
+
90
+ def validate
91
+ (1..500).include? options[:width]
92
+ end
93
+
94
+ ### Chaining tasks in strategies
95
+
96
+ run_task [CropTask, ResizeTask, ZipTask, EmailTask], options
97
+
98
+ Tasks can be chained and run sequentially as a unit. The input, options and report will be passed down the chain to the last task. The final, composed output and report is then returned to the main strategy and available in the strategy Report.
99
+
100
+ You can use task chains to process elements in a collection:
101
+
102
+ BIG = {:width => 500, :height => 500}
103
+ MEDIUM = {:width => 200, :height => 200}
104
+ SMALL = {:width => 100, :height => 100}
105
+
106
+ with_input some_file_here
107
+
108
+ # Crop the original
109
+ run_task CropTask, {:square => true}
110
+
111
+ # Produce resized versions and store them somewhere
112
+ [BIG, MEDIUM, SMALL].each do |dimensions|
113
+ run_task [ResizeTask, ZipTask, StorageTask], dimensions
114
+ end
115
+
116
+ If any task in the chain fails the error will be logged. The following tasks will not be processed.
117
+
118
+ ### Stand alone tasks
119
+
120
+ Tasks are quite simple objects. They expect an array with an input, an options hash and a report hash as an argument and return the same.
121
+
122
+ resize = ResizeTask.new([some_file, {:width => 100, :height => 100}, {}])
123
+ resize.run # => [input, options, report]
124
+
125
+ The task report (just a hash) is populated by tasks and passed along to the next task in the chain, if any. This is a good place to put data resulting from your processing that you want to make available for the next task. You do this with the *report!* shortcut within the process method or any other method you define in your task.
126
+
127
+ # ... do some processing
128
+ report! :some_key, 'Some value'
129
+
130
+ If a task expects a certain key in the report passed from a previous task in a chain, you can make it explicit just like with options:
131
+
132
+ expects_report_with :some_key
133
+
134
+ If :some_key doesn't exist in the passed report, the task will not process and halt any chain it is in.
135
+
136
+ ### Logging
137
+
138
+ Default to STDOUT. Just add your own logger and logger level.
139
+
140
+ ProtoProcessor.logger = Logger.new('my_processor.log')
141
+ ProtoProcessor.logger.level = Logger::ERROR
142
+
143
+ ## TODO
144
+
145
+ * Improve DSL in strategies
146
+ * Better log formatting?
147
+
148
+ ## Copyright
149
+
150
+ Copyright (c) 2009 Ismael Celis. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "proto_processor"
8
+ gem.summary = %Q{TODO}
9
+ gem.email = "ismaelct@gmail.com"
10
+ gem.homepage = "http://github.com/ismasan/proto_processor"
11
+ gem.authors = ["Ismael Celis"]
12
+
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
18
+
19
+ require 'spec/rake/spectask'
20
+ Spec::Rake::SpecTask.new(:spec) do |spec|
21
+ spec.libs << 'lib' << 'spec'
22
+ spec.spec_files = FileList['spec/**/*_spec.rb']
23
+ end
24
+
25
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.pattern = 'spec/**/*_spec.rb'
28
+ spec.rcov = true
29
+ end
30
+
31
+
32
+ task :default => :spec
33
+
34
+ require 'rake/rdoctask'
35
+ Rake::RDocTask.new do |rdoc|
36
+ if File.exist?('VERSION.yml')
37
+ config = YAML.load(File.read('VERSION.yml'))
38
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
39
+ else
40
+ version = ""
41
+ end
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "proto_processor #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
48
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 3
4
+ :patch: 0
@@ -0,0 +1,16 @@
1
+ require 'logger'
2
+ module ProtoProcessor
3
+ ROOT = File.dirname(__FILE__)
4
+ $: << File.join(ROOT, 'proto_processor')
5
+
6
+ autoload :Task, 'task'
7
+ autoload :Strategy, 'strategy'
8
+ autoload :TaskRunner, 'task_runner'
9
+ autoload :Report, 'report'
10
+
11
+ class << self
12
+ attr_accessor :logger
13
+ end
14
+ end
15
+ ProtoProcessor.logger = Logger.new(STDOUT)
16
+ ProtoProcessor.logger.level = Logger::ERROR
@@ -0,0 +1,77 @@
1
+ module ProtoProcessor
2
+ class Report
3
+ include Enumerable
4
+
5
+ attr_reader :chain_outputs, :chain_tasks, :error, :chain_statuses
6
+
7
+ def initialize
8
+ @chain_outputs = {}
9
+ @chain_tasks = {}
10
+
11
+ @chain_statuses = []
12
+ @error = nil
13
+ end
14
+
15
+ # == Store tasks and output after running task chain
16
+ #
17
+ def report(chain_key, tasks, output)
18
+ report_output(chain_key, output)
19
+ report_tasks(chain_key, tasks)
20
+ end
21
+
22
+ def each(&block)
23
+ @chain_outputs.each &block
24
+ end
25
+
26
+ def [](chain_key)
27
+ @chain_outputs[chain_key]
28
+ end
29
+
30
+ # Major fail at strategy level
31
+ #
32
+ def fail!(exception)
33
+ @error = exception
34
+ end
35
+
36
+ def successful?
37
+ @error.nil?
38
+ end
39
+
40
+ def run_report
41
+ @run_report ||= begin
42
+ rep = {}
43
+ @chain_tasks.each do |task_name, runs|
44
+ runs.each_with_index do |r, i|
45
+ rep[:"#{task_name}_#{i}"] = r.map{|t| "#{t.class.name}: #{t.report[:status]}"}
46
+ end
47
+ end
48
+ rep
49
+ end
50
+ end
51
+
52
+ protected
53
+
54
+ # {
55
+ # :FooTask => [out1,out2],
56
+ # :BarTask => [out1,out2]
57
+ # }
58
+ def report_output(chain_key, output)
59
+ @chain_outputs[chain_key] ||= []
60
+ @chain_outputs[chain_key] << output
61
+ end
62
+
63
+ # {
64
+ # :FooTask => [[task1,task2],[task1,task2]],
65
+ # :BarTask => [[task1,task2],[task1,task2]]
66
+ # }
67
+
68
+
69
+ def report_tasks(chain_key, tasks)
70
+ @chain_tasks[chain_key] ||= []
71
+ @chain_tasks[chain_key] << [*tasks]
72
+
73
+ @chain_statuses << tasks.map{|t|t.successful?}
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,51 @@
1
+ module ProtoProcessor::Strategy
2
+
3
+ def report
4
+ @report ||= ProtoProcessor::Report.new
5
+ end
6
+
7
+ def runner
8
+ @task_runner ||= ProtoProcessor::TaskRunner.new(report)
9
+ end
10
+
11
+ def run
12
+ begin
13
+ ProtoProcessor.logger.info "Running strategy #{self.class.name}"
14
+ process
15
+ rescue StandardError => e
16
+ report.fail!(e)
17
+ ProtoProcessor.logger.error e.class.name
18
+ ProtoProcessor.logger.debug e.message + "\n" + e.backtrace.join("\n")
19
+ end
20
+ yield report if block_given?
21
+ report
22
+ end
23
+
24
+ def process
25
+ raise NotImplementedError, "You must implement #process in your strategies"
26
+ end
27
+
28
+ def with_input(input)
29
+ @current_input = input#.dup # dup so we don't overwrite passed input later on
30
+ end
31
+
32
+ def current_input
33
+ @current_input ||= ''
34
+ end
35
+
36
+ # === Run a task and update input and report (but don't update options)
37
+ # If passed and array of options, run task for each option hash
38
+ #
39
+ def run_task(task_class, options = nil, &block)
40
+ return false if options.nil?
41
+ run_task_chain([*task_class], options, &block)
42
+ end
43
+
44
+ protected
45
+
46
+ def run_task_chain(task_classes, options, &block)
47
+ chain_key = task_classes.first.name.split('::').last.to_sym
48
+ @current_input, temp_options, task_report = runner.run_chain(chain_key, task_classes,current_input, options, {}, &block)
49
+ end
50
+
51
+ end
@@ -0,0 +1,158 @@
1
+ require 'set'
2
+ module ProtoProcessor
3
+
4
+ module Task
5
+
6
+ FAILURE = 'FAILURE'
7
+ SUCCESS = 'SUCCESS'
8
+
9
+ class InvalidTaskError < StandardError
10
+ def message
11
+ "Invalid task"
12
+ end
13
+ end
14
+
15
+ class MissingParametersError < InvalidTaskError
16
+ def initialize(errors)
17
+ @errors = errors
18
+ super
19
+ end
20
+
21
+ def message
22
+ "Missing parameters: #{@errors.inspect}"
23
+ end
24
+ end
25
+
26
+ def self.included(base)
27
+ base.class_eval do
28
+ attr_reader :input, :options, :report, :error
29
+ extend Validations
30
+ end
31
+ #base.extend Validations
32
+ end
33
+
34
+ # new([input, options, report])
35
+ def initialize(args)
36
+ raise ArgumentError, "You must provide an Enumerable object as argument" unless args.respond_to?(:each)
37
+ raise ArgumentError, "You must provide an array with input, options and report" if args.size < 3
38
+ raise ArgumentError, "A task report must be or behave like a Hash" unless args.last.respond_to?(:[]=)
39
+ @input, @options, @report = args[0], args[1].dup, args[2].dup
40
+ @success = false
41
+ @error = nil
42
+ end
43
+
44
+ # class HaltedChainError < StandardError
45
+ # def message
46
+ # "Task not run because previous task failed"
47
+ # end
48
+ # end
49
+
50
+ def run
51
+ begin
52
+ log_halt_task and return false if report[:status] == FAILURE
53
+ run_validations!
54
+ before_process
55
+ process
56
+ report!(:status, SUCCESS)
57
+ @success = true
58
+ after_process
59
+ rescue StandardError => e
60
+ # horrible horrible hack to allow Rspec exceptions to bubble up.
61
+ # we can't rescue them because RSpec is only included when running specs
62
+ raise if e.class.name =~ /Spec/
63
+ report!(:status, FAILURE)
64
+ report!(:error, {:name => e.class.name, :message => e.message})
65
+ @error = e
66
+ ProtoProcessor.logger.error "#{self.class.name}: #{e.class.name} => #{e.message}"
67
+ ProtoProcessor.logger.debug e.backtrace.join("\n")
68
+ end
69
+ [@input, @options, @report]
70
+ end
71
+
72
+ def successful?
73
+ @success
74
+ end
75
+
76
+ # === Validate in subclasses
77
+ # Example:
78
+ # def validate
79
+ # options[:some].nil?
80
+ # end
81
+ #
82
+ def valid?
83
+ run_validations!
84
+ true
85
+ rescue InvalidTaskError => e
86
+ false
87
+ end
88
+
89
+ # Abstract
90
+ #
91
+ def process
92
+ raise NotImplementedError, "You need to implement #process in you tasks"
93
+ end
94
+
95
+ def before_process
96
+ true
97
+ end
98
+
99
+ def after_process
100
+ true
101
+ end
102
+
103
+ # Update input so it's passed to the next task
104
+ #
105
+ def update_input!(new_input)
106
+ @input = new_input
107
+ end
108
+
109
+ protected
110
+
111
+ def log_halt_task
112
+ ProtoProcessor.logger.info "#{self.class.name} not run because previous task failed"
113
+ end
114
+
115
+ def validate
116
+ # implement this in subclasses if needed
117
+ true
118
+ end
119
+
120
+ def run_validations!
121
+ raise InvalidTaskError unless validate
122
+ errors = []
123
+ self.class.validations.each do |key, required|
124
+ provided = send(key).keys
125
+ missing = required - provided
126
+ errors << "#{key} => #{missing.inspect}" unless required.to_set.subset?(provided.to_set)
127
+ end
128
+ raise MissingParametersError.new(errors) unless errors.empty?
129
+ end
130
+
131
+ def report!(key, value)
132
+ @report[key] = value
133
+ end
134
+
135
+ module Validations
136
+ def expects_options_with(*args)
137
+ store_validations_for(:options, args)
138
+ end
139
+
140
+ def expects_report_with(*args)
141
+ store_validations_for(:report, args)
142
+ end
143
+
144
+ # {
145
+ # :options => [:field1, :field2],
146
+ # :report => [:field1, :field2]
147
+ # }
148
+ def store_validations_for(key, args)
149
+ validations[key] ||= []
150
+ validations[key] += args
151
+ end
152
+
153
+ def validations
154
+ @validations ||= {}
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,24 @@
1
+ module ProtoProcessor
2
+ class TaskRunner
3
+
4
+ attr_reader :report
5
+
6
+ def initialize(report)
7
+ @report = report
8
+ end
9
+
10
+ def run_chain(run_key, task_classes, initial_input, options, initial_report, &block)
11
+ tasks = []
12
+ output = task_classes.inject([initial_input, options, initial_report]) do |args, task_class|
13
+ task = task_class.new(args)
14
+ tasks << task
15
+ task.run
16
+ end
17
+ block.call(tasks, output.first, output.last) if block_given?
18
+ @report.report(run_key, tasks, output)
19
+ output
20
+ end
21
+
22
+
23
+ end
24
+ end
@@ -0,0 +1,112 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ module BAS
4
+ class BAS::FooTask
5
+ include ProtoProcessor::Task
6
+ def process
7
+ @input << 'FOO'
8
+ report!(:hello, @input)
9
+ end
10
+ end
11
+
12
+ class BAS::BarTask
13
+ include ProtoProcessor::Task
14
+ def process
15
+ @input << 'BAR'
16
+ report!(:hello, @input)
17
+ end
18
+ end
19
+ end
20
+
21
+ class FooBarStrategy
22
+ include ProtoProcessor::Strategy
23
+
24
+ def options
25
+ options = {
26
+ "original" => 'sample_image.jpg',
27
+ "type" => "FooBar",
28
+ "rotate" => 90,
29
+ "crop" => 'crop_options',
30
+ "sizes" => [{'width' => 100}, {'width' => 200}, {'width' => 300}]
31
+ }
32
+ end
33
+
34
+ def process
35
+ run_task BAS::FooTask, options
36
+ run_task BAS::BarTask, options
37
+ options['sizes'].each do |size_params|
38
+ run_task BAS::FooTask, size_params
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+
45
+ describe "Strategy" do
46
+ before do
47
+ @strategy = FooBarStrategy.new
48
+ @options = @strategy.options
49
+ end
50
+ describe 'running tasks with #run_task' do
51
+ before do
52
+ @mock_task = mock('task', :run => true, :valid? => true, :successful? => true)
53
+ end
54
+
55
+ it "should have a task runner" do
56
+ @strategy.runner.should be_kind_of(ProtoProcessor::TaskRunner)
57
+ end
58
+
59
+ it "should have an initial report" do
60
+ @strategy.report.should be_kind_of(ProtoProcessor::Report)
61
+ end
62
+
63
+ it "should delegate to Task Runner with run key, array of one task, input, options and report" do
64
+ @strategy.runner.should_receive(:run_chain).with(:FooTask, [BAS::FooTask], @strategy.current_input, @options, {})
65
+ @strategy.run_task BAS::FooTask, @options
66
+ end
67
+
68
+ it "should be able to change the input for upcoming tasks" do
69
+ new_input = 'a different input'
70
+ @strategy.runner.should_receive(:run_chain).with(:FooTask, [BAS::FooTask], new_input, @options, {})
71
+ @strategy.with_input new_input
72
+ @strategy.run_task BAS::FooTask, @options
73
+ end
74
+
75
+ it "should run a task with default input, options and report" do
76
+ BAS::FooTask.should_receive(:new).with([@strategy.current_input,@options,{}]).and_return @mock_task
77
+ @strategy.run_task BAS::FooTask, @options
78
+ end
79
+
80
+ it "should not run a task if passed options are NIL" do
81
+ BAS::FooTask.should_not_receive(:new)
82
+ @strategy.run_task BAS::FooTask
83
+ end
84
+
85
+ it "should run a task with passed options" do
86
+ opts = {:blah => 1}
87
+ BAS::FooTask.should_receive(:new).with([@strategy.current_input,opts,{}]).and_return @mock_task
88
+ @strategy.run_task BAS::FooTask, opts
89
+ end
90
+
91
+ end
92
+
93
+ describe 'running main process' do
94
+ before do
95
+ @mock_task = mock('task', :run => [@input, @options, {}], :valid? => true)
96
+ end
97
+
98
+ it "should accumulate result of tasks" do
99
+ pending 'this belongs in the task runner'
100
+ BAS::FooTask.should_receive(:new).exactly(4).and_return @mock_task
101
+ BAS::BarTask.should_receive(:new).exactly(1).and_return @mock_task
102
+ @strategy.run
103
+ end
104
+
105
+ it "should update values" do
106
+ report = @strategy.run
107
+ @strategy.current_input.should == 'FOOBARFOOFOOFOO'
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,181 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ class FooTask
4
+ include ProtoProcessor::Task
5
+ def process
6
+ @input << 'a'
7
+ report! :foo, 'foo'
8
+ end
9
+ end
10
+
11
+ class InvalidTask
12
+ include ProtoProcessor::Task
13
+ def process
14
+ @input = "I got here"
15
+ end
16
+
17
+ def validate
18
+ false
19
+ end
20
+ end
21
+
22
+ describe "Task" do
23
+ before do
24
+ @input = ''
25
+ @options = {}
26
+ @report = {}
27
+ @task = FooTask.new([@input, @options, @report])
28
+ end
29
+
30
+ it "should raise if less than 3 arguments" do
31
+ lambda {
32
+ FooTask.new(['bar'])
33
+ }.should raise_error(ArgumentError)
34
+ end
35
+
36
+ it "should raise if argument is not enumerable" do
37
+ lambda {
38
+ FooTask.new(1)
39
+ }.should raise_error(ArgumentError)
40
+ end
41
+
42
+ it "should raise if report does not cuack like a Hash" do
43
+ lambda {
44
+ FooTask.new(['',{},10])
45
+ }.should raise_error(ArgumentError)
46
+ end
47
+
48
+ it "should be valid by default" do
49
+ @task.valid?.should be_true
50
+ end
51
+
52
+ it "should have input, options and report" do
53
+ @task.input.should == @input
54
+ @task.options.should == @options
55
+ @task.report.should == @report
56
+ end
57
+
58
+ it "should not be successful before running" do
59
+ @task.successful?.should_not be_true
60
+ end
61
+
62
+ it "should update input with #update_input!" do
63
+ @task.input.should == ''
64
+ @task.update_input!(2)
65
+ @task.input.should == 2
66
+ end
67
+
68
+ describe "invalid tasks" do
69
+ before do
70
+ @invalid_task = InvalidTask.new(['', {}, @report])
71
+ end
72
+
73
+ it "should be invalid (duh!)" do
74
+ @invalid_task.valid?.should be_false
75
+ end
76
+
77
+ it "should not process if not valid" do
78
+ # if the input is modified, it means that it did run #process
79
+ # we can't use expectations because I'm rescueing exceptions, including RSpec ones!
80
+ output = @invalid_task.run
81
+ output.first.should be_empty
82
+ end
83
+
84
+ end
85
+
86
+ describe 'task receiving failed report' do
87
+ before do
88
+ @task = FooTask.new([@input, @options, {:status => 'FAILURE'}])
89
+ end
90
+
91
+ it "should halt processing" do
92
+ @task.should_not_receive(:process)
93
+ @task.run
94
+ @task.should_not be_successful
95
+ @task.report[:status].should == 'FAILURE'
96
+ end
97
+ end
98
+
99
+ describe "running" do
100
+
101
+ it "should invoke :process" do
102
+ @task.should_receive(:process)
103
+ @task.run
104
+ end
105
+
106
+ it "should return modified input" do
107
+ output = @task.run
108
+ output[0].should == 'a'
109
+ end
110
+
111
+ it "should return options" do
112
+ output = @task.run
113
+ output[1].should == @options
114
+ end
115
+
116
+ it "should add stuff to report" do
117
+ output = @task.run
118
+ output[2].should == {:status => 'SUCCESS', :foo => 'foo'}
119
+ end
120
+
121
+ it "should be successful after running (successfully)" do
122
+ @task.run
123
+ @task.successful?.should be_true
124
+ end
125
+
126
+ end
127
+
128
+ describe 'process callbacks' do
129
+ before :all do
130
+ class ATask
131
+ include ProtoProcessor::Task
132
+ def before_process
133
+ @input.before
134
+ end
135
+ def process
136
+ @input.process
137
+ end
138
+ def after_process
139
+ @input.after
140
+ end
141
+ end
142
+ @input = mock('input', :before =>1, :process => 2, :after => 3)
143
+
144
+ end
145
+
146
+ it "should invoke after and before process callbacks" do
147
+ @input.should_receive(:before)
148
+ @input.should_receive(:process)
149
+ @input.should_receive(:after)
150
+ ATask.new([@input, {}, {}]).run
151
+ end
152
+ end
153
+
154
+ end
155
+
156
+ class BarTask
157
+ include ProtoProcessor::Task
158
+ def process
159
+ @input << 'b'
160
+ report! :bar, 'bar'
161
+ end
162
+ end
163
+
164
+ describe "decorating the same or other tasks" do
165
+
166
+ it "should iteratively process" do
167
+ input, options, report = '', {}, {}
168
+ 1.upto(5) do |i|
169
+ task = FooTask.new([input, options, report])
170
+ old_input = input
171
+ input, options, report = task.run
172
+ old_input.object_id.should == input.object_id
173
+ end
174
+
175
+ task = BarTask.new([input, options, report])
176
+ input, options, report = task.run
177
+
178
+ input.should == 'aaaaab'
179
+ report.should == {:bar=>"bar", :status=>"SUCCESS", :foo=>"foo"}
180
+ end
181
+ end
@@ -0,0 +1,9 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe 'Report' do
4
+ before do
5
+ @report = ProtoProcessor::Report.new
6
+ end
7
+
8
+ it "should"
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'proto_processor'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
@@ -0,0 +1,55 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ module TVS
4
+ class MockTask
5
+ include ProtoProcessor::Task
6
+ end
7
+ class FooTask < MockTask
8
+
9
+ expects_options_with :id, :name
10
+
11
+ expects_report_with :url, :path
12
+
13
+ def process
14
+ puts options[:id]
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ describe 'Task validations' do
21
+ describe "with valid parameters" do
22
+ before do
23
+ @task = TVS::FooTask.new(['',{:id => 1, :name => 'bar'},{:url => 'abc', :path => 'aaa'}])
24
+ end
25
+
26
+ it "should be valid" do
27
+ @task.should be_valid
28
+ end
29
+ end
30
+
31
+ describe "with missing parameters" do
32
+ before do
33
+ @task = TVS::FooTask.new(['',{:id => 1},{:path => 'aaa'}])
34
+ end
35
+
36
+ it "should not be valid" do
37
+ @task.should_not be_valid
38
+ end
39
+
40
+ it "should not run" do
41
+ @task.should_not_receive(:process)
42
+ @task.run
43
+ end
44
+
45
+ it "should pass status report = FAILURE" do
46
+ @task.run
47
+ @task.report[:status].should == ProtoProcessor::Task::FAILURE
48
+ end
49
+
50
+ it "should have error information" do
51
+ @task.run
52
+ @task.report[:error].should_not be_nil
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,80 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ module RunnerSpecHelper
4
+ class Task1
5
+ include ProtoProcessor::Task
6
+ def process
7
+ report! :common, 1
8
+ report! :a, 1
9
+ @input << 'a'
10
+ end
11
+ end
12
+ class Task2
13
+ include ProtoProcessor::Task
14
+ def process
15
+ report! :common, 2
16
+ report! :b, 2
17
+ @input << 'b'
18
+ end
19
+ end
20
+ class Task3
21
+ include ProtoProcessor::Task
22
+ def process
23
+ report! :common, 3
24
+ report! :c, 3
25
+ @input << 'c'
26
+ end
27
+ end
28
+ end
29
+ include RunnerSpecHelper
30
+ include ProtoProcessor
31
+
32
+ describe '@runner' do
33
+ before do
34
+ @input, @options, @task_report = '', {}, {}
35
+ @report = mock('report', :report => true)
36
+ @runner = TaskRunner.new(@report)
37
+ end
38
+ describe 'running tasks with #run_chain' do
39
+
40
+ before do
41
+ @task1 = mock('task1', :run => [@input, @options, {:common => 1}])
42
+ @task2 = mock('task2', :run => [@input, @options, {:common => 2}])
43
+ @task3 = mock('task3', :run => [@input, @options, {:common => 3}])
44
+ end
45
+
46
+ it "should run one task" do
47
+ Task1.should_receive(:new).with([@input, @options, @task_report]).and_return @task1
48
+ @runner.run_chain(:foo,[Task1], @input, @options, @task_report).should == ["", {}, {:common=>1}]
49
+ end
50
+
51
+ it "should register task and output with report object" do
52
+ Task1.stub!(:new).and_return @task1
53
+ @report.should_receive(:report).with(:foo, [@task1], ["", {}, {:common=>1}])
54
+ @runner.run_chain(:foo,[Task1], @input, @options, @task_report)
55
+ end
56
+
57
+ it "should run sequence of nested tasks" do
58
+ Task1.should_receive(:new).with([@input, @options, @task_report]).and_return @task1
59
+ Task2.should_receive(:new).with([@input, @options, @task_report.merge(:common => 1)]).and_return @task2
60
+ Task3.should_receive(:new).with([@input, @options, @task_report.merge(:common => 2)]).and_return @task3
61
+
62
+ @runner.run_chain(:foo,[Task1, Task2, Task3], @input, @options, @task_report)
63
+
64
+ end
65
+
66
+ end
67
+
68
+ it "should call an optional block with run tasks, final output and consolidated report" do
69
+ collaborator = mock('collaborator')
70
+ collaborator.should_receive(:do_something!).with(2, "ab", {:b=>2, :status=>"SUCCESS", :common=>2, :a=>1})
71
+ @runner.run_chain(:foo,[Task1, Task2], @input, @options, @task_report) do |tasks, output, report|
72
+ collaborator.do_something!(tasks.size, output, report)
73
+ end
74
+ end
75
+
76
+ it "verifies that result is merged hash" do
77
+ @runner.run_chain(:foo,[Task1, Task2, Task3], @input, @options, @task_report)\
78
+ .should == ['abc', @options, {:common => 3, :status => 'SUCCESS', :a =>1, :b =>2, :c => 3}]
79
+ end
80
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: newbamboo-proto_processor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Ismael Celis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-26 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: ismaelct@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.markdown
25
+ files:
26
+ - LICENSE
27
+ - README.markdown
28
+ - Rakefile
29
+ - VERSION.yml
30
+ - lib/proto_processor.rb
31
+ - lib/proto_processor/report.rb
32
+ - lib/proto_processor/strategy.rb
33
+ - lib/proto_processor/task.rb
34
+ - lib/proto_processor/task_runner.rb
35
+ - spec/base_strategy_spec.rb
36
+ - spec/base_task_spec.rb
37
+ - spec/report_spec.rb
38
+ - spec/spec_helper.rb
39
+ - spec/task_validations_spec.rb
40
+ - spec/tasks_runner_spec.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/ismasan/proto_processor
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.2.0
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: TODO
67
+ test_files:
68
+ - spec/base_strategy_spec.rb
69
+ - spec/base_task_spec.rb
70
+ - spec/report_spec.rb
71
+ - spec/spec_helper.rb
72
+ - spec/task_validations_spec.rb
73
+ - spec/tasks_runner_spec.rb