processor 1.0.0 → 2.0.0

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 (41) hide show
  1. data/.coveralls.yml +1 -0
  2. data/.gitignore +1 -0
  3. data/README.md +68 -41
  4. data/example/migrator.rb +2 -0
  5. data/lib/processor/data/array_processor.rb +4 -0
  6. data/lib/processor/data/null_processor.rb +7 -10
  7. data/lib/processor/environment.rb +3 -0
  8. data/lib/processor/event_processor.rb +25 -0
  9. data/lib/processor/logger_messages.rb +56 -0
  10. data/lib/processor/messenger.rb +16 -3
  11. data/lib/processor/observer/logger.rb +43 -34
  12. data/lib/processor/observer/null_observer.rb +8 -3
  13. data/lib/processor/process_runner/threads.rb +3 -8
  14. data/lib/processor/runner.rb +8 -23
  15. data/lib/processor/subroutine/counter.rb +25 -0
  16. data/lib/processor/subroutine/name.rb +28 -0
  17. data/lib/processor/subroutine/recursion.rb +29 -0
  18. data/lib/processor/thread.rb +5 -1
  19. data/lib/processor/version.rb +1 -1
  20. data/lib/processor.rb +2 -1
  21. data/processor.gemspec +6 -4
  22. data/spec/processor/data/array_processor_spec.rb +1 -2
  23. data/spec/processor/data/null_processor_spec.rb +27 -2
  24. data/spec/processor/event_processor_spec.rb +58 -0
  25. data/spec/processor/logger_messages_spec.rb +69 -0
  26. data/spec/processor/messenger_spec.rb +15 -0
  27. data/spec/processor/observer/logger_spec.rb +47 -16
  28. data/spec/processor/observer/null_observer_spec.rb +33 -0
  29. data/spec/processor/process_runner/specs.rb +12 -23
  30. data/spec/processor/process_runner/threads_spec.rb +1 -3
  31. data/spec/processor/runner_spec.rb +28 -54
  32. data/spec/processor/subroutine/counter_spec.rb +44 -0
  33. data/spec/processor/subroutine/name_spec.rb +23 -0
  34. data/spec/processor/subroutine/recursion_spec.rb +23 -0
  35. data/spec/processor/thread_spec.rb +1 -1
  36. data/spec/spec_helper_lite.rb +7 -2
  37. data/spec/support/dummy_file +0 -0
  38. metadata +47 -12
  39. data/lib/processor/data/solr_processor.rb +0 -23
  40. data/lib/processor/events_registrator.rb +0 -16
  41. data/spec/processor/events_registrator_spec.rb +0 -15
@@ -0,0 +1,28 @@
1
+ require 'delegate'
2
+
3
+ module Processor
4
+ module Subroutine
5
+ class Name < ::SimpleDelegator
6
+ def name
7
+ return super if __getobj__.respond_to? :name
8
+
9
+ # underscore a class name
10
+ @name ||= real_object.class.name.to_s.
11
+ gsub(/::/, '_').
12
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
13
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
14
+ downcase
15
+ end
16
+
17
+ private
18
+ def real_object
19
+ object = __getobj__
20
+ while object.is_a? SimpleDelegator
21
+ object = object.__getobj__
22
+ end
23
+
24
+ object
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'counter'
2
+ require 'delegate'
3
+
4
+ module Processor
5
+ module Subroutine
6
+ class Recursion < ::SimpleDelegator
7
+ def initialize(processor)
8
+ # recursion depends on counter subroutine
9
+ processor = Counter.new(processor) unless processor.respond_to? :processed_records_count
10
+
11
+ super processor
12
+ end
13
+
14
+ def process(*)
15
+ recursion_preventer
16
+ super
17
+ end
18
+
19
+ private
20
+ def recursion_preventer
21
+ raise Exception, "Processing fall into recursion. Check logs." if processed_records_count >= max_records_to_process
22
+ end
23
+
24
+ def max_records_to_process
25
+ @max_records_to_process ||= (total_records * 1.1).round + 10
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,11 +1,15 @@
1
1
  require 'processor/runner'
2
+ require 'processor/event_processor'
2
3
  require 'processor/process_runner/successive'
3
4
  require 'processor/process_runner/threads'
5
+ require 'processor/subroutine/recursion'
4
6
 
5
7
  module Processor
6
8
  class Thread
7
9
  def initialize(data_processor, *observers)
8
- @runner = Runner.new data_processor, EventsRegistrator.new(observers)
10
+ data_processor = Subroutine::Recursion.new(data_processor)
11
+
12
+ @runner = Runner.new EventProcessor.new(data_processor, observers)
9
13
  end
10
14
 
11
15
  def run_as(&process_runner)
@@ -1,3 +1,3 @@
1
1
  module Processor
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0"
3
3
  end
data/lib/processor.rb CHANGED
@@ -1,10 +1,11 @@
1
+ require "processor/environment"
1
2
  require "processor/version"
2
3
 
3
4
  require "processor/data/array_processor"
4
5
  require "processor/data/batch_processor"
5
6
  require "processor/data/null_processor"
6
- require "processor/data/solr_processor"
7
7
  require "processor/data/solr_pages_processor"
8
+ require "processor/data/csv_processor"
8
9
 
9
10
  require "processor/observer/logger"
10
11
  require "processor/observer/null_observer"
data/processor.gemspec CHANGED
@@ -2,15 +2,15 @@
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'processor/version'
5
+ require 'processor/environment'
5
6
 
6
7
  Gem::Specification.new do |gem|
7
8
  gem.name = "processor"
8
9
  gem.version = Processor::VERSION
9
10
  gem.authors = ["Alexander Paramonov"]
10
11
  gem.email = ["alexander.n.paramonov@gmail.com"]
11
- gem.summary = %q{Universal processor for data migration and reports generation.}
12
- gem.description = %q{Processor could execute any DataProcessor you specify and log entire process.
13
- You may add own observers for monitoring background tasks on even send email to bussiness with generated report.}
12
+ gem.summary = %q{Universal processor for iteration over a collection with threads, logging and post processing}
13
+ gem.description = %q{Processor is a tool that helps to iterate over collection and perform complex actions on a result. It is extremely useful in data migrations, report generation, etc. }
14
14
  gem.homepage = "http://github.com/AlexParamonov/processor"
15
15
  gem.license = "MIT"
16
16
 
@@ -21,6 +21,8 @@ Gem::Specification.new do |gem|
21
21
 
22
22
  gem.add_development_dependency "rake"
23
23
  gem.add_development_dependency "rspec", ">= 2.6"
24
- gem.add_development_dependency "pry"
24
+ gem.add_development_dependency "pry" unless Processor::RUNNING_ON_CI
25
+ gem.add_development_dependency "pry-plus" if "ruby" == RUBY_ENGINE && false == Processor::RUNNING_ON_CI
26
+ gem.add_development_dependency "coveralls" if Processor::RUNNING_ON_CI
25
27
  end
26
28
 
@@ -3,8 +3,7 @@ require 'processor/data/array_processor'
3
3
 
4
4
  describe Processor::Data::ArrayProcessor do
5
5
  it "should have total records count equals to count of records" do
6
- records = *1..5
7
- subject.stub(records: records)
6
+ subject.stub(records: 1..5)
8
7
  subject.total_records.should eq 5
9
8
  end
10
9
  end
@@ -1,9 +1,34 @@
1
1
  require 'spec_helper_lite'
2
2
  require 'processor/data/null_processor'
3
+ require 'processor/runner'
4
+ require 'processor/process_runner/successive'
3
5
 
4
6
  describe Processor::Data::NullProcessor do
5
- it "should have a name equals to underscored class name" do
6
- subject.name.should eq "processor_data_null_processor"
7
+ it "should have zero records" do
8
+ subject.records.should be_empty
9
+ subject.total_records.should be_zero
10
+ end
11
+
12
+ describe "required methods" do
13
+ it "should respond to all happy path methods" do
14
+ collector = Class.new do
15
+ def records; [1] end
16
+ def method_missing(method, *)
17
+ @methods = @methods.to_a << method
18
+ end
19
+ attr_reader :methods
20
+ end.new
21
+
22
+ runner = Processor::Runner.new collector
23
+ runner.run Processor::ProcessRunner::Successive.new
24
+ collector.methods.each do |method|
25
+ subject.should respond_to method
26
+ end
27
+ end
28
+
29
+ %w[record_error error total_records].each do |method|
30
+ it { subject.should respond_to method }
31
+ end
7
32
  end
8
33
  end
9
34
 
@@ -0,0 +1,58 @@
1
+ require 'spec_helper_lite'
2
+ require 'processor/event_processor'
3
+
4
+ describe Processor::EventProcessor do
5
+ let(:processor) { stub.as_null_object }
6
+ let(:observer) { stub.as_null_object }
7
+ let(:observers) { [observer] }
8
+ subject { Processor::EventProcessor.new(processor, observers) }
9
+
10
+
11
+ it "should delegate methods to processor" do
12
+ processor.should_receive(:method_name).and_return("result")
13
+ subject.method_name.should eq "result"
14
+ end
15
+
16
+ it "should send a processor by update method" do
17
+ observer.should_receive(:update).with(/method_name/, processor)
18
+ subject.method_name
19
+ end
20
+
21
+ describe "before and after events" do
22
+ it "should fire before_method_name event before calling processor.method_name" do
23
+ observer.should_receive(:update).ordered do |event|
24
+ event.should eq :before_method_name
25
+ end
26
+ processor.should_receive(:method_name).ordered
27
+ subject.method_name
28
+ end
29
+
30
+ it "should fire after_method_name event after calling processor.method_name" do
31
+ observer.should_receive(:update)
32
+ processor.should_receive(:method_name, processor).ordered
33
+ observer.should_receive(:update).ordered do |event|
34
+ event.should eq :after_method_name
35
+ end
36
+ subject.method_name
37
+ end
38
+
39
+ it "should send result of processing to after_ event" do
40
+ result = stub
41
+ processor.stub(method_name: result)
42
+ observer.should_receive(:update).with(:after_method_name, processor, result).ordered
43
+ subject.method_name
44
+ end
45
+ end
46
+
47
+ describe "broadcasting of events" do
48
+ let(:observers) do
49
+ 3.times.map do
50
+ stub(:observer).tap { |observer| observer.should_receive(:update).with(:test_event, processor).once }
51
+ end
52
+ end
53
+
54
+ it "should broadcast events to all observers" do
55
+ subject.register :test_event
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,69 @@
1
+ require "spec_helper_lite"
2
+ require "logger"
3
+ require "processor/logger_messages"
4
+
5
+ describe Processor::LoggerMessages do
6
+ let(:messages) { Processor::LoggerMessages.new logger }
7
+ shared_examples_for "a null logger" do
8
+ it "should not produce any output" do
9
+ messages.finished.should eq ""
10
+ messages.initialized.should eq ""
11
+ end
12
+ end
13
+
14
+ describe "Ruby Logger" do
15
+ describe "file" do
16
+ let(:filename) { "spec/support/dummy_file" }
17
+ let(:logger) { Logger.new File.new(filename) }
18
+
19
+ it "should include file name into message" do
20
+ messages.finished.should include filename
21
+ messages.initialized.should include filename
22
+ end
23
+ end
24
+
25
+ describe "filename" do
26
+ let(:filename) { "spec/support/dummy_file" }
27
+ let(:logger) { Logger.new filename }
28
+
29
+ it "should include file name into message" do
30
+ messages.finished.should include filename
31
+ messages.initialized.should include filename
32
+ end
33
+ end
34
+
35
+ describe "null" do
36
+ let(:logger) { ::Logger.new(File::NULL) }
37
+ it_behaves_like "a null logger"
38
+ end
39
+
40
+ describe "STDOUT" do
41
+ let(:logger) { Logger.new(STDOUT) }
42
+
43
+ it "should include IO in the message" do
44
+ messages.initialized.should include "IO"
45
+ messages.finished.should eq ""
46
+ end
47
+ end
48
+
49
+ describe "STDERR" do
50
+ let(:logger) { Logger.new(STDERR) }
51
+
52
+ it "should include IO in the message" do
53
+ messages.initialized.should include "IO"
54
+ messages.finished.should eq ""
55
+ end
56
+ end
57
+ end
58
+
59
+ describe "nil" do
60
+ let(:logger) { nil }
61
+ it_behaves_like "a null logger"
62
+ end
63
+
64
+ describe "unknown object" do
65
+ let(:logger) { Object.new }
66
+ it_behaves_like "a null logger"
67
+ end
68
+
69
+ end
@@ -21,6 +21,21 @@ describe Processor::Messenger do
21
21
  messenger.fatal "fatal error"
22
22
  end
23
23
 
24
+ it "should not send empty messages" do
25
+ io.should_not_receive(:write)
26
+ messenger = Processor::Messenger.new :info, io
27
+ [nil, ""].each do |empty_message|
28
+ messenger.message empty_message
29
+ end
30
+ end
31
+
32
+ it "should send messages from a name of sender" do
33
+ sender = "File Logger"
34
+ io.should_receive(:write).with /#{sender}/
35
+ messenger = Processor::Messenger.new :info, io, sender
36
+ messenger.error "failed to create log file"
37
+ end
38
+
24
39
  describe "messages" do
25
40
  let(:messenger) { Processor::Messenger.new :debug, io }
26
41
  %w[debug info error fatal].each do |message_level|
@@ -2,31 +2,62 @@ require 'spec_helper_lite'
2
2
  require 'processor/observer/logger'
3
3
 
4
4
  describe Processor::Observer::Logger do
5
+ subject { Processor::Observer::Logger.new logger, messenger: messenger, messages: messages, processor: processor }
6
+
5
7
  let(:processor) { stub.as_null_object }
6
- let(:no_messages) { ::Logger.new("/dev/null") }
8
+ let(:messages) { stub.as_null_object }
9
+ let(:messenger) { ::Logger.new("/dev/null") }
10
+ let(:logger) { ::Logger.new("/dev/null") }
11
+
12
+ describe "logger" do
13
+ describe "as proc" do
14
+ let(:external_logger) { stub }
15
+ let(:logger) { -> name { external_logger } }
16
+
17
+ it "should be assepted" do
18
+ subject.logger.should eq external_logger
19
+ end
20
+ end
21
+
22
+ describe "as object" do
23
+ let(:external_logger) { stub }
24
+ let(:logger) { external_logger }
25
+
26
+ it "should be assepted" do
27
+ subject.logger.should eq external_logger
28
+ end
29
+ end
7
30
 
8
- subject { Processor::Observer::Logger }
9
- it "accepts logger builder as parameter" do
10
- external_logger = mock
11
- logger_observer = subject.new -> name { external_logger }, messenger: no_messages
31
+ describe "nil" do
32
+ let(:logger) { nil }
12
33
 
13
- external_logger.should_receive(:info)
14
- logger_observer.processing_started processor
34
+ it "uses ruby Logger" do
35
+ ruby_logger = stub.as_null_object
36
+ observer = subject
37
+ ::Logger.should_receive(:new).and_return(ruby_logger)
38
+ observer.logger.should eq ruby_logger
39
+ end
15
40
  end
41
+ end
16
42
 
17
- it "accepts logger as parameter" do
18
- external_logger = mock
19
- logger_observer = subject.new external_logger, messenger: no_messages
43
+ describe "messages" do
44
+ describe "as object" do
45
+ let(:messages) { stub }
20
46
 
21
- external_logger.should_receive(:info)
22
- logger_observer.processing_started processor
47
+ it "should be assepted" do
48
+ messages.should_receive(:initialized)
49
+ subject.after_start nil
50
+ end
23
51
  end
24
52
 
25
- it "use ruby Logger if no external logger provided" do
26
- logger_observer = subject.new nil, messenger: no_messages
53
+ describe "as hash" do
54
+ let(:messages) { { initialized: "INIT" } }
27
55
 
28
- Logger.should_receive(:new).and_return(stub.as_null_object)
29
- logger_observer.processing_started processor
56
+ it "should be assepted" do
57
+ messenger.should_receive(:info).with("INIT")
58
+ subject.after_start nil
59
+ end
30
60
  end
61
+ end
31
62
  end
32
63
 
@@ -0,0 +1,33 @@
1
+ require 'spec_helper_lite'
2
+ require 'processor/observer/null_observer'
3
+
4
+ describe Processor::Observer::NullObserver do
5
+
6
+ it "should send update message to itself if know how to respond to this message" do
7
+ subject.should_receive(:know_how)
8
+ subject.update :know_how
9
+ end
10
+
11
+ it "should ignore update message if dont know how to respond to this message" do
12
+ subject.stub(:respond_to?).with(:unknown_method).and_return(false)
13
+ subject.should_not_receive(:unknown_method)
14
+ subject.update :unknown_method
15
+ end
16
+
17
+ it "should blow up if got undefined method call" do
18
+ expect { subject.undefined_method }.to raise_error(NoMethodError)
19
+ end
20
+
21
+ describe "processor" do
22
+ let(:processor) { stub }
23
+ it "should set a processor" do
24
+ observer = Processor::Observer::NullObserver.new processor: processor
25
+ observer.processor.should eq processor
26
+ end
27
+
28
+ specify "update method should set a processor" do
29
+ subject.update :some_method, processor
30
+ subject.processor.should eq processor
31
+ end
32
+ end
33
+ end
@@ -1,49 +1,38 @@
1
1
  shared_examples_for "a records processor" do
2
2
  let(:process_runner) { described_class.new }
3
3
  let(:records) { 1..2 }
4
- let(:processor) { stub.tap { |p| p.stub(records: records) } }
5
-
6
- let(:no_recursion_preventer) { Proc.new{} }
7
- let(:no_events) { stub.as_null_object }
4
+ let(:processor) { stub.tap { |p| p.stub(records: records) }.as_null_object }
8
5
 
9
6
  it "should fetch records from processor" do
10
7
  processor.should_receive(:records).and_return([])
11
- process_runner.call processor, no_events, no_recursion_preventer
8
+ process_runner.call processor
12
9
  end
13
10
 
14
11
  it "should send each found record to processor" do
15
12
  records.each { |record| processor.should_receive(:process).with(record) }
16
- process_runner.call processor, no_events, no_recursion_preventer
13
+ process_runner.call processor
17
14
  end
18
15
 
19
16
  describe "exception handling" do
20
17
  describe "processing a record raised StandardError" do
18
+ let(:records) { 1..3 }
19
+
21
20
  it "should continue processing" do
22
- processor.should_receive(:process).twice.and_raise(StandardError)
23
- expect { process_runner.call processor, no_events, no_recursion_preventer }.to_not raise_error
21
+ processor.should_receive(:process).exactly(3).times.and_raise(StandardError)
22
+ expect { process_runner.call processor }.to_not raise_error
24
23
  end
25
24
 
26
- it "should register a record_processing_error event" do
27
- event = mock.tap { |event| event.should_receive(:trigger).any_number_of_times }
28
-
29
- events_registrator = stub
30
- events_registrator.should_receive(:register) do |event_name, failed_record, exception|
31
- next if event_name != :record_processing_error
32
- event_name.should eq :record_processing_error
33
- exception.should be_a StandardError
34
- event.trigger
35
- end.any_number_of_times
36
-
37
- processor.stub(:process).and_raise(StandardError)
38
-
39
- process_runner.call processor, events_registrator, no_recursion_preventer rescue nil
25
+ it "should call record_error" do
26
+ processor.should_receive(:process).at_least(1).and_raise(StandardError)
27
+ processor.should_receive(:record_error).at_least(1)
28
+ process_runner.call processor
40
29
  end
41
30
  end
42
31
 
43
32
  describe "processing a record raised Exception" do
44
33
  it "should break processing" do
45
34
  processor.should_receive(:process).once.and_raise(Exception)
46
- expect { process_runner.call processor, no_events, no_recursion_preventer }.to raise_error(Exception)
35
+ expect { process_runner.call processor }.to raise_error(Exception)
47
36
  end
48
37
  end
49
38
  end
@@ -3,8 +3,6 @@ require_relative 'specs'
3
3
  require 'processor/process_runner/threads'
4
4
 
5
5
  describe Processor::ProcessRunner::Threads do
6
- let(:no_recursion_preventer) { Proc.new{} }
7
- let(:no_events) { stub.as_null_object }
8
6
  it_behaves_like "a records processor"
9
7
 
10
8
  it "should run in defined number of threads" do
@@ -20,6 +18,6 @@ describe Processor::ProcessRunner::Threads do
20
18
  processor.stub(records: 1..9)
21
19
  processor.should_receive(:process).exactly(9).times
22
20
 
23
- process_runner.call(processor, no_events, no_recursion_preventer)
21
+ process_runner.call processor
24
22
  end
25
23
  end
@@ -2,70 +2,44 @@ require 'spec_helper_lite'
2
2
  require 'processor/runner'
3
3
 
4
4
  describe Processor::Runner do
5
- let(:runner) { Processor::Runner.new(processor, events_registrator) }
6
- let(:processor) { stub }
7
- let(:events_registrator) { stub.as_null_object }
5
+ let(:runner) { Processor::Runner.new(processor) }
6
+ let(:processor) { stub.as_null_object }
8
7
  let(:no_process_runner) { Proc.new{} }
9
8
 
10
- describe "exception handling" do
9
+ it "should start processing" do
10
+ processor.should_receive(:start)
11
+ runner.run no_process_runner
12
+ end
13
+
14
+ it "should finish processing" do
15
+ processor.should_receive(:finish)
16
+ runner.run no_process_runner
17
+ end
18
+
19
+ it "should finalize processing" do
20
+ processor.should_receive(:finalize)
21
+ runner.run no_process_runner
22
+ end
23
+
24
+ describe "processing error" do
11
25
  describe "processing records raised" do
26
+ let(:process_runner) { Proc.new { raise RuntimeError } }
27
+
12
28
  it "should break processing and rerise" do
13
29
  expect do
14
- runner.run Proc.new { raise RuntimeError }
30
+ runner.run process_runner
15
31
  end.to raise_error(RuntimeError)
16
32
  end
17
33
 
18
- it "should register a processing_error event" do
19
- register_processing_error_event mock.tap { |event| event.should_receive :trigger }
20
- runner.run Proc.new { raise RuntimeError } rescue nil
34
+ it "should send error to processor" do
35
+ processor.should_receive(:error)
36
+ runner.run process_runner rescue nil
21
37
  end
22
- end
23
-
24
- private
25
- def register_processing_error_event(event)
26
- # Check that processing_error event was register
27
- events_registrator.should_receive(:register) do |event_name, current_processor, exception|
28
- next if event_name != :processing_error
29
- event_name.should eq :processing_error
30
- current_processor.should eq processor
31
- exception.should be_a RuntimeError
32
- event.trigger
33
- end.any_number_of_times
34
- end
35
- end
36
-
37
- describe "recursion" do
38
- before(:each) do
39
- processor.stub(total_records: 100)
40
- processor.stub(records: 1..Float::INFINITY)
41
- end
42
-
43
- it "should not fall into recursion" do
44
- processor.should_receive(:process).at_most(1000).times
45
38
 
46
- expect do
47
- process_runner = Proc.new do |data_processor, events, recursion_preventer|
48
- data_processor.records.each do |record|
49
- recursion_preventer.call
50
- data_processor.process record
51
- end
52
- end
53
- runner.run process_runner
54
- end.to raise_error(Exception, /Processing fall into recursion/)
55
- end
56
-
57
- it "should have 10% + 10 rerurns window" do
58
- processor.should_receive(:process).exactly(120).times
59
-
60
- expect do
61
- process_runner = Proc.new do |data_processor, events, recursion_preventer|
62
- data_processor.records.each do |record|
63
- recursion_preventer.call
64
- data_processor.process record
65
- end
66
- end
67
- runner.run process_runner
68
- end.to raise_error(Exception, /Processing fall into recursion/)
39
+ it "should finalize processing" do
40
+ processor.should_receive(:finalize)
41
+ runner.run process_runner rescue nil
42
+ end
69
43
  end
70
44
  end
71
45
  end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper_lite'
2
+ require 'processor/subroutine/counter'
3
+
4
+ describe Processor::Subroutine::Counter do
5
+ let(:records) { 1..10 }
6
+ let(:processor) { stub(total_records: 10, process: true) }
7
+ let(:subroutine) { Processor::Subroutine::Counter.new processor }
8
+
9
+ it "should count remaining records" do
10
+ subroutine.remaining_records_count.should eq 10
11
+ records.each do
12
+ expect { subroutine.process }.to change { subroutine.remaining_records_count }.by -1
13
+ end
14
+ end
15
+
16
+ it "should count processed records" do
17
+ subroutine.processed_records_count.should eq 0
18
+ records.each do
19
+ expect { subroutine.process }.to change { subroutine.processed_records_count }.by 1
20
+ end
21
+ end
22
+
23
+ describe "over process" do
24
+ before(:each) do
25
+ 10.times { subroutine.process }
26
+ end
27
+
28
+ specify "remaining_records_count should be zero" do
29
+ subroutine.remaining_records_count.should eq 0
30
+ 5.times do
31
+ expect { subroutine.process }.to_not change { subroutine.remaining_records_count }.from 0
32
+ end
33
+ end
34
+
35
+ specify "processed_records_count should keep counting" do
36
+ subroutine.processed_records_count.should eq 10
37
+ 5.times do
38
+ expect { subroutine.process }.to change { subroutine.processed_records_count }.by 1
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+