processor 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+