exel 1.2.1 → 1.5.2

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 (66) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +4 -4
  3. data/.gitignore +1 -2
  4. data/.rubocop.yml +23 -14
  5. data/.rubocop_airbnb.yml +2 -0
  6. data/.rubocop_todo.yml +1 -13
  7. data/.travis.yml +26 -0
  8. data/Gemfile +2 -2
  9. data/Gemfile.lock +118 -0
  10. data/Guardfile +1 -0
  11. data/README.md +96 -31
  12. data/Rakefile +2 -0
  13. data/exel.gemspec +7 -7
  14. data/lib/exel.rb +7 -1
  15. data/lib/exel/ast_node.rb +6 -10
  16. data/lib/exel/context.rb +4 -1
  17. data/lib/exel/deferred_context_value.rb +3 -1
  18. data/lib/exel/error/job_termination.rb +12 -0
  19. data/lib/exel/events.rb +6 -0
  20. data/lib/exel/instruction.rb +5 -2
  21. data/lib/exel/instruction_node.rb +2 -0
  22. data/lib/exel/job.rb +8 -4
  23. data/lib/exel/listen_instruction.rb +2 -0
  24. data/lib/exel/logging.rb +24 -1
  25. data/lib/exel/logging/logger_wrapper.rb +31 -0
  26. data/lib/exel/logging_helper.rb +36 -0
  27. data/lib/exel/middleware/chain.rb +67 -0
  28. data/lib/exel/middleware/logging.rb +30 -0
  29. data/lib/exel/null_instruction.rb +2 -0
  30. data/lib/exel/processor_helper.rb +9 -1
  31. data/lib/exel/processors/async_processor.rb +2 -8
  32. data/lib/exel/processors/run_processor.rb +2 -6
  33. data/lib/exel/processors/split_processor.rb +15 -10
  34. data/lib/exel/providers/local_file_provider.rb +9 -6
  35. data/lib/exel/providers/threaded_async_provider.rb +2 -0
  36. data/lib/exel/remote_value.rb +11 -0
  37. data/lib/exel/sequence_node.rb +2 -0
  38. data/lib/exel/value.rb +2 -0
  39. data/lib/exel/version.rb +3 -1
  40. data/spec/exel/ast_node_spec.rb +48 -27
  41. data/spec/exel/context_spec.rb +77 -77
  42. data/spec/exel/deferred_context_value_spec.rb +42 -42
  43. data/spec/exel/events_spec.rb +68 -59
  44. data/spec/exel/instruction_node_spec.rb +17 -16
  45. data/spec/exel/instruction_spec.rb +49 -42
  46. data/spec/exel/job_spec.rb +99 -84
  47. data/spec/exel/listen_instruction_spec.rb +11 -10
  48. data/spec/exel/logging/logger_wrapper_spec.rb +93 -0
  49. data/spec/exel/logging_helper_spec.rb +24 -0
  50. data/spec/exel/logging_spec.rb +69 -24
  51. data/spec/exel/middleware/chain_spec.rb +65 -0
  52. data/spec/exel/middleware/logging_spec.rb +31 -0
  53. data/spec/exel/middleware_spec.rb +68 -0
  54. data/spec/exel/null_instruction_spec.rb +4 -4
  55. data/spec/exel/processors/async_processor_spec.rb +17 -18
  56. data/spec/exel/processors/run_processor_spec.rb +10 -11
  57. data/spec/exel/processors/split_processor_spec.rb +99 -74
  58. data/spec/exel/providers/local_file_provider_spec.rb +26 -28
  59. data/spec/exel/providers/threaded_async_provider_spec.rb +37 -38
  60. data/spec/exel/sequence_node_spec.rb +12 -11
  61. data/spec/exel/value_spec.rb +33 -33
  62. data/spec/exel_spec.rb +9 -7
  63. data/spec/integration/integration_spec.rb +3 -1
  64. data/spec/spec_helper.rb +4 -2
  65. data/spec/support/integration_test_classes.rb +4 -3
  66. metadata +37 -48
@@ -1,14 +1,15 @@
1
- module EXEL
2
- describe ListenInstruction do
3
- subject(:instruction) { EXEL::ListenInstruction.new(:event, listener) }
4
- let(:listener) { double(:listener) }
5
- let(:context) { EXEL::Context.new }
1
+ # frozen_string_literal: true
6
2
 
7
- describe '#execute' do
8
- it 'registers the event listener' do
9
- expect(instruction).to receive(:register_listener).with(context, :event, listener)
10
- instruction.execute(context)
11
- end
3
+ describe EXEL::ListenInstruction do
4
+ subject(:instruction) { EXEL::ListenInstruction.new(:event, listener) }
5
+
6
+ let(:listener) { double(:listener) }
7
+ let(:context) { EXEL::Context.new }
8
+
9
+ describe '#execute' do
10
+ it 'registers the event listener' do
11
+ expect(instruction).to receive(:register_listener).with(context, :event, listener)
12
+ instruction.execute(context)
12
13
  end
13
14
  end
14
15
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe EXEL::Logging::LoggerWrapper do
4
+ subject(:wrapper) { EXEL::Logging::LoggerWrapper.new(logger) }
5
+
6
+ let(:logger) { instance_double(Logger) }
7
+
8
+ it { is_expected.to be_a(SimpleDelegator) }
9
+
10
+ LOG_LEVELS = %i(debug info warn error fatal unknown).freeze # rubocop:disable Airbnb/SpecConstantAssignment
11
+
12
+ context 'without a Logging prefix' do
13
+ LOG_LEVELS.each do |level|
14
+ describe "##{level}" do
15
+ context 'when passed a message string' do
16
+ it 'passes the message to its wrapped logger' do
17
+ expect(logger).to receive(level).with('message')
18
+ wrapper.send(level, 'message')
19
+ end
20
+ end
21
+
22
+ context 'when passed a message block' do
23
+ it 'passes the block to its wrapped logger' do
24
+ block = proc {}
25
+ expect(logger).to receive(level).with(nil, &block)
26
+
27
+ wrapper.send(level, &block)
28
+ end
29
+
30
+ context 'and a progname' do
31
+ it 'passes the block and progname to its wrapped logger' do
32
+ block = proc {}
33
+ expect(logger).to receive(level).with('test', &block)
34
+
35
+ wrapper.send(level, 'test', &block)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#add' do
43
+ it 'passes the message to its wrapped logger' do
44
+ expect(logger).to receive(:add).with(Logger::FATAL, 'message', 'progname')
45
+ wrapper.add(Logger::FATAL, 'message', 'progname')
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'with a Logging prefix' do
51
+ before { allow(EXEL::Logging).to receive(:prefix).and_return('prefix: ') }
52
+
53
+ LOG_LEVELS.each do |level|
54
+ describe "##{level}" do
55
+ context 'when passed a message string' do
56
+ it 'passes the prefixed message to its wrapped logger' do
57
+ expect(logger).to receive(level).with('prefix: message')
58
+ wrapper.send(level, 'message')
59
+ end
60
+ end
61
+
62
+ context 'when passed a message block' do
63
+ it 'passes the prefixed block to its wrapped logger' do
64
+ expect(logger).to receive(level) do |progname, &block|
65
+ expect(progname).to be_nil
66
+ expect(block.call).to eq('prefix: message')
67
+ end
68
+
69
+ wrapper.send(level) { 'message' }
70
+ end
71
+
72
+ context 'and a progname' do
73
+ it 'passes the prefixed block and progname to its wrapped logger' do
74
+ expect(logger).to receive(level) do |progname, &block|
75
+ expect(progname).to eq('test')
76
+ expect(block.call).to eq('prefix: message')
77
+ end
78
+
79
+ wrapper.send(level, 'test') { 'message' }
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#add' do
87
+ it 'passes the prefixed message to its wrapped logger' do
88
+ expect(logger).to receive(:add).with(Logger::FATAL, 'prefix: message', 'progname')
89
+ wrapper.add(Logger::FATAL, 'message', 'progname')
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe EXEL::LoggingHelper do
4
+ class HelperClass
5
+ include EXEL::LoggingHelper
6
+ end
7
+
8
+ let(:helper) { HelperClass.new }
9
+
10
+ describe '#logger' do
11
+ it 'returns EXEL.logger' do
12
+ expect(helper.logger).to eq(EXEL.logger)
13
+ end
14
+ end
15
+
16
+ %i(debug info warn error fatal).each do |level|
17
+ describe "#log_#{level}" do
18
+ it "logs a #{level} message" do
19
+ expect(EXEL.logger).to receive(level).with('test')
20
+ helper.send("log_#{level}", 'test')
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,35 +1,80 @@
1
- module EXEL
2
- describe Logging do
3
- before { @restore_logger = Logging.logger }
4
- after { Logging.logger = @restore_logger }
5
-
6
- describe '.logger=' do
7
- it 'sets a logger' do
8
- logger = double(:logger)
9
- Logging.logger = logger
10
- expect(Logging.logger).to be(logger)
1
+ # frozen_string_literal: true
2
+
3
+ describe EXEL::Logging do
4
+ before { @restore_logger = EXEL::Logging.logger }
5
+
6
+ after { EXEL::Logging.logger = @restore_logger }
7
+
8
+ describe EXEL::Logging::PrefixFormatter do
9
+ context 'without a Logging prefix' do
10
+ it 'formats the message' do
11
+ expect(subject.call('warn', Time.new(2007, 11, 1, 15, 25, 0, '+09:00'), 'test', 'message'))
12
+ .to eq("2007-11-01 06:25:00 UTC severity=warn, message\n")
11
13
  end
14
+ end
12
15
 
13
- it 'sets a null logger when nil given' do
14
- expect(Logger).to receive(:new).with('/dev/null')
15
- Logging.logger = nil
16
+ context 'with a Logging prefix' do
17
+ it 'adds the prefix to the formatted message' do
18
+ EXEL::Logging.with_prefix('[prefix] ') do
19
+ expect(subject.call('warn', Time.new(2007, 11, 1, 15, 25, 0, '+09:00'), 'test', 'message'))
20
+ .to eq("2007-11-01 06:25:00 UTC severity=warn, [prefix] message\n")
21
+ end
16
22
  end
17
23
  end
24
+ end
25
+
26
+ describe '.logger=' do
27
+ it 'sets a wrapped logger' do
28
+ logger = instance_double(Logger)
29
+ EXEL::Logging.logger = logger
30
+ expect(EXEL::Logging.logger).to be_a(EXEL::Logging::LoggerWrapper)
31
+ expect(EXEL::Logging.logger.__getobj__).to be(logger)
32
+ end
33
+
34
+ it 'sets a null logger when nil given' do
35
+ expect(Logger).to receive(:new).with('/dev/null')
36
+ EXEL::Logging.logger = nil
37
+ end
38
+ end
18
39
 
19
- describe '.logger' do
20
- before { Logging.instance_variable_set(:@logger, nil) }
40
+ describe '.logger' do
41
+ before { EXEL::Logging.instance_variable_set(:@logger, nil) }
21
42
 
22
- it 'initializes the logger on first read if not already set' do
23
- EXEL.configure do |config|
24
- config.log_level = :warn
25
- config.log_filename = 'log.txt'
26
- end
43
+ it 'initializes the logger on first read if not already set' do
44
+ EXEL.configure do |config|
45
+ config.log_level = :warn
46
+ config.log_filename = 'log.txt'
47
+ end
48
+
49
+ logger = instance_double(Logger)
50
+ expect(Logger).to receive(:new).with('log.txt').and_return(logger)
51
+ expect(logger).to receive(:level=).with(Logger::WARN)
52
+ expect(logger).to receive(:formatter=).with(EXEL::Logging::PrefixFormatter)
53
+
54
+ EXEL::Logging.logger
55
+ end
56
+ end
57
+
58
+ describe '.with_prefix' do
59
+ it 'sets the prefix before yielding to the block and clears it after' do
60
+ expect(EXEL::Logging.prefix).to be_nil
27
61
 
28
- logger = instance_double(Logger)
29
- expect(Logger).to receive(:new).with('log.txt').and_return(logger)
30
- expect(logger).to receive(:level=).with(Logger::WARN)
62
+ EXEL::Logging.with_prefix('testing') do
63
+ expect(EXEL::Logging.prefix).to eq('testing')
64
+ end
65
+
66
+ expect(EXEL::Logging.prefix).to be_nil
67
+ end
68
+
69
+ it 'handles nesting' do
70
+ EXEL::Logging.with_prefix('outer') do
71
+ expect(EXEL::Logging.prefix).to eq('outer')
72
+
73
+ EXEL::Logging.with_prefix('inner') do
74
+ expect(EXEL::Logging.prefix).to eq('inner')
75
+ end
31
76
 
32
- Logging.logger
77
+ expect(EXEL::Logging.prefix).to eq('outer')
33
78
  end
34
79
  end
35
80
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe EXEL::Middleware::Chain do
4
+ subject(:chain) { EXEL::Middleware::Chain.new }
5
+
6
+ class TestMiddleware
7
+ def initialize(*)
8
+ end
9
+
10
+ def call
11
+ end
12
+ end
13
+
14
+ class AnotherTestMiddleware < TestMiddleware
15
+ end
16
+
17
+ describe '#add' do
18
+ it 'adds a middleware to the chain' do
19
+ chain.add(TestMiddleware)
20
+ expect(chain).to include(TestMiddleware)
21
+ end
22
+
23
+ it 'removes and adds to end if already present' do
24
+ chain.add(TestMiddleware)
25
+ chain.add(AnotherTestMiddleware)
26
+ chain.add(TestMiddleware)
27
+ expect(chain.entries.map(&:klass)).to eq([AnotherTestMiddleware, TestMiddleware])
28
+ end
29
+ end
30
+
31
+ describe '#remove' do
32
+ it 'removes a middleware from the chain' do
33
+ chain.add(TestMiddleware)
34
+ chain.add(AnotherTestMiddleware)
35
+ chain.remove(TestMiddleware)
36
+ expect(chain.entries.map(&:klass)).to eq([AnotherTestMiddleware])
37
+ end
38
+ end
39
+
40
+ describe '#include?' do
41
+ it 'returns true if the middleware class is in the chain' do
42
+ chain.add(TestMiddleware)
43
+ expect(chain).to include(TestMiddleware)
44
+ end
45
+
46
+ it 'returns false if the middleware class is not in the chain' do
47
+ chain.add(TestMiddleware)
48
+ expect(chain).not_to include(AnotherTestMiddleware)
49
+ end
50
+ end
51
+
52
+ describe '#invoke' do
53
+ it 'calls each middleware' do
54
+ chain.add(TestMiddleware)
55
+ expect_any_instance_of(TestMiddleware).to receive(:call).with(1, 2, 3)
56
+ chain.invoke(1, 2, 3)
57
+ end
58
+
59
+ it 'initializes middleware with given constructor arguments' do
60
+ chain.add(TestMiddleware, 1, 2)
61
+ expect(TestMiddleware).to receive(:new).with(1, 2).and_return(TestMiddleware.new)
62
+ chain.invoke
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe EXEL::Middleware::Logging do
4
+ class TestProcessor
5
+ end
6
+
7
+ describe '#call' do
8
+ it 'yields to the given block' do
9
+ called = false
10
+
11
+ subject.call(Object, {}, {}) do
12
+ called = true
13
+ end
14
+
15
+ expect(called).to be_truthy
16
+ end
17
+
18
+ it 'logs with context[:log_prefix] and the processor class as the prefix' do
19
+ expect(EXEL::Logging).to receive(:with_prefix).with('[prefix][TestProcessor] ')
20
+ subject.call(TestProcessor, {log_prefix: '[prefix]'}, {}) {}
21
+ end
22
+
23
+ it 'raises rescued exceptions' do
24
+ expect do
25
+ subject.call(Object, {}, {}) do
26
+ raise Exception, 're-raise me'
27
+ end
28
+ end.to raise_error Exception, 're-raise me'
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe EXEL::Middleware do
4
+ class IORecorder
5
+ def initialize(input, output)
6
+ @input = input
7
+ @output = output
8
+ end
9
+
10
+ def call(_processor, context, args)
11
+ @input << args[:input]
12
+ yield
13
+ @output << context[:output]
14
+ end
15
+ end
16
+
17
+ class RescueErrors
18
+ def call(_processor, _context, _args)
19
+ yield
20
+ rescue
21
+ nil
22
+ end
23
+ end
24
+
25
+ class AddProcessor
26
+ def initialize(context)
27
+ @context = context
28
+ end
29
+
30
+ def process(_)
31
+ @context[:output] = @context[:input] + 1
32
+ raise 'rescue me'
33
+ end
34
+ end
35
+
36
+ before :all do # rubocop:disable RSpec/BeforeAfterAll
37
+ EXEL::Job.define :middleware_test_job do
38
+ process with: AddProcessor, input: 1
39
+ end
40
+ end
41
+
42
+ let(:input) { [] }
43
+ let(:output) { [] }
44
+
45
+ before do
46
+ EXEL.configure do |config|
47
+ config.middleware.add(IORecorder, input, output)
48
+ config.middleware.add(RescueErrors)
49
+ end
50
+ end
51
+
52
+ after do
53
+ EXEL.configure do |config|
54
+ config.middleware.remove(IORecorder)
55
+ config.middleware.remove(RescueErrors)
56
+ end
57
+ end
58
+
59
+ it 'can configure custom middleware' do
60
+ expect(EXEL.configuration.middleware.entries.map(&:klass)).to eq([IORecorder, RescueErrors])
61
+ end
62
+
63
+ it 'invokes the middleware around the processor' do
64
+ EXEL::Job.run(:middleware_test_job)
65
+ expect(input).to eq([1])
66
+ expect(output).to eq([2])
67
+ end
68
+ end
@@ -1,5 +1,5 @@
1
- module EXEL
2
- describe NullInstruction do
3
- it { is_expected.to respond_to :execute }
4
- end
1
+ # frozen_string_literal: true
2
+
3
+ describe EXEL::NullInstruction do
4
+ it { is_expected.to respond_to :execute }
5
5
  end
@@ -1,24 +1,23 @@
1
- module EXEL
2
- module Processors
3
- describe AsyncProcessor do
4
- subject(:processor) { described_class.new(context) }
5
- let(:context) { EXEL::Context.new }
6
- let(:block) { instance_double(SequenceNode) }
1
+ # frozen_string_literal: true
7
2
 
8
- before do
9
- allow(EXEL).to receive(:async_provider).and_return(EXEL::Providers::DummyAsyncProvider)
10
- end
3
+ describe EXEL::Processors::AsyncProcessor do
4
+ subject(:processor) { described_class.new(context) }
11
5
 
12
- it 'looks up the async provider on initialization' do
13
- expect(processor.provider).to be_an_instance_of(EXEL::Providers::DummyAsyncProvider)
14
- end
6
+ let(:context) { EXEL::Context.new }
7
+ let(:block) { instance_double(EXEL::SequenceNode) }
15
8
 
16
- describe '#process' do
17
- it 'calls do_async on the async provider' do
18
- expect(processor.provider).to receive(:do_async).with(block)
19
- processor.process(block)
20
- end
21
- end
9
+ before do
10
+ allow(EXEL).to receive(:async_provider).and_return(EXEL::Providers::DummyAsyncProvider)
11
+ end
12
+
13
+ it 'looks up the async provider on initialization' do
14
+ expect(processor.provider).to be_an_instance_of(EXEL::Providers::DummyAsyncProvider)
15
+ end
16
+
17
+ describe '#process' do
18
+ it 'calls do_async on the async provider' do
19
+ expect(processor.provider).to receive(:do_async).with(block)
20
+ processor.process(block)
22
21
  end
23
22
  end
24
23
  end