exel 1.4.0 → 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +4 -4
  3. data/.rubocop.yml +20 -12
  4. data/.rubocop_airbnb.yml +2 -0
  5. data/.travis.yml +17 -4
  6. data/Gemfile +1 -2
  7. data/Gemfile.lock +64 -60
  8. data/README.md +1 -1
  9. data/Rakefile +1 -0
  10. data/exel.gemspec +4 -4
  11. data/lib/exel.rb +1 -0
  12. data/lib/exel/ast_node.rb +2 -1
  13. data/lib/exel/context.rb +2 -1
  14. data/lib/exel/deferred_context_value.rb +1 -0
  15. data/lib/exel/error/job_termination.rb +1 -0
  16. data/lib/exel/events.rb +1 -0
  17. data/lib/exel/instruction.rb +2 -1
  18. data/lib/exel/instruction_node.rb +1 -0
  19. data/lib/exel/job.rb +6 -4
  20. data/lib/exel/listen_instruction.rb +1 -0
  21. data/lib/exel/logging.rb +1 -0
  22. data/lib/exel/logging/logger_wrapper.rb +4 -1
  23. data/lib/exel/logging_helper.rb +1 -0
  24. data/lib/exel/middleware/chain.rb +1 -0
  25. data/lib/exel/middleware/logging.rb +1 -1
  26. data/lib/exel/null_instruction.rb +1 -0
  27. data/lib/exel/processor_helper.rb +1 -0
  28. data/lib/exel/processors/run_processor.rb +1 -0
  29. data/lib/exel/processors/split_processor.rb +2 -1
  30. data/lib/exel/providers/local_file_provider.rb +2 -1
  31. data/lib/exel/providers/threaded_async_provider.rb +1 -0
  32. data/lib/exel/sequence_node.rb +1 -0
  33. data/lib/exel/value.rb +1 -0
  34. data/lib/exel/version.rb +2 -1
  35. data/spec/exel/ast_node_spec.rb +42 -42
  36. data/spec/exel/context_spec.rb +76 -77
  37. data/spec/exel/deferred_context_value_spec.rb +41 -42
  38. data/spec/exel/events_spec.rb +65 -65
  39. data/spec/exel/instruction_node_spec.rb +16 -16
  40. data/spec/exel/instruction_spec.rb +46 -45
  41. data/spec/exel/job_spec.rb +94 -91
  42. data/spec/exel/listen_instruction_spec.rb +10 -10
  43. data/spec/exel/logging/logger_wrapper_spec.rb +67 -69
  44. data/spec/exel/logging_helper_spec.rb +15 -16
  45. data/spec/exel/logging_spec.rb +56 -56
  46. data/spec/exel/middleware/chain_spec.rb +51 -53
  47. data/spec/exel/middleware/logging_spec.rb +21 -23
  48. data/spec/exel/middleware_spec.rb +49 -50
  49. data/spec/exel/null_instruction_spec.rb +3 -4
  50. data/spec/exel/processors/async_processor_spec.rb +16 -18
  51. data/spec/exel/processors/run_processor_spec.rb +9 -11
  52. data/spec/exel/processors/split_processor_spec.rb +91 -93
  53. data/spec/exel/providers/local_file_provider_spec.rb +25 -28
  54. data/spec/exel/providers/threaded_async_provider_spec.rb +36 -38
  55. data/spec/exel/sequence_node_spec.rb +11 -11
  56. data/spec/exel/value_spec.rb +32 -33
  57. data/spec/exel_spec.rb +8 -7
  58. data/spec/integration/integration_spec.rb +2 -1
  59. data/spec/spec_helper.rb +3 -2
  60. data/spec/support/integration_test_classes.rb +3 -1
  61. metadata +16 -30
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Logging
4
5
  # Wraps calls to a logger to add {Logging} prefix to log messages
@@ -20,7 +21,9 @@ module EXEL
20
21
  end
21
22
 
22
23
  def add(severity, message = nil, progname = nil)
23
- message = yield if message.nil? && block_given?
24
+ if message.nil? && block_given?
25
+ message = yield
26
+ end
24
27
  __getobj__.add(severity, "#{Logging.prefix}#{message}", progname)
25
28
  end
26
29
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # Logging related helper methods for processors
4
5
  module LoggingHelper
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # Middleware is code configured to run around each processor execution. Custom middleware can be added as follows:
4
5
  #
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Middleware
4
5
  # Middleware to add a prefix to all messages logged during processor execution. The prefix is specified by the
@@ -12,7 +13,6 @@ module EXEL
12
13
 
13
14
  def log_process
14
15
  start_time = Time.now
15
- EXEL.logger.info 'Starting'
16
16
 
17
17
  yield
18
18
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # An {Instruction} that does nothing when executed
4
5
  class NullInstruction
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # Helper methods useful to processors
4
5
  # @deprecated Most functionality replaced by {EXEL::Middleware::Logging} middleware.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Processors
4
5
  # Implements the +run+ instruction.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'csv'
3
4
  require 'tempfile'
4
5
  require_relative '../logging_helper'
@@ -65,7 +66,7 @@ module EXEL
65
66
  def process_file(callback)
66
67
  csv_options = @context[:csv_options] || {col_sep: ','}
67
68
 
68
- CSV.foreach(@file.path, csv_options) do |line|
69
+ CSV.foreach(@file.path, **csv_options) do |line|
69
70
  process_line(line, callback)
70
71
 
71
72
  break if @tempfile_count == @max_chunks
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Providers
4
5
  # The default remote provider. Doesn't actually upload and download files to and from remote storage, but rather
@@ -14,7 +15,7 @@ module EXEL
14
15
  end
15
16
 
16
17
  def self.remote?(uri)
17
- uri =~ %r{file://}
18
+ uri.to_s =~ %r{file://}
18
19
  end
19
20
  end
20
21
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  module Providers
4
5
  # The default remote provider. Provides async execution by running the given EXEL block in a new Thread
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require_relative './ast_node'
3
4
 
4
5
  module EXEL
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
4
  # Contains methods to handle remote and local values. Used for {Context} serialization
4
5
  module Value
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module EXEL
3
- VERSION = '1.4.0'
4
+ VERSION = '1.5.1'
4
5
  end
@@ -1,61 +1,61 @@
1
1
  # frozen_string_literal: true
2
- module EXEL
3
- describe ASTNode do
4
- let(:context) { instance_double(EXEL::Context) }
5
2
 
6
- def instruction
7
- instance_double(Instruction, execute: nil)
8
- end
3
+ describe EXEL::ASTNode do
4
+ let(:context) { instance_double(EXEL::Context) }
9
5
 
10
- TestNode = Class.new(ASTNode)
6
+ def instruction
7
+ instance_double(EXEL::Instruction, execute: nil)
8
+ end
11
9
 
12
- describe '#start' do
13
- context 'when a JobTermination error bubbles up' do
14
- let(:node) { TestNode.new(instruction) }
10
+ class TestNode < EXEL::ASTNode
11
+ end
15
12
 
16
- before do
17
- allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination, 'Error')
18
- end
13
+ describe '#start' do
14
+ context 'when a JobTermination error bubbles up' do
15
+ let(:node) { TestNode.new(instruction) }
19
16
 
20
- it 'ensures the process fails silently' do
21
- expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
22
- expect { node.start(context) }.not_to raise_error
23
- end
17
+ before do
18
+ allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination, 'Error')
19
+ end
24
20
 
25
- it 'logs the error by default' do
26
- expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
27
- node.start(context)
28
- end
21
+ it 'ensures the process fails silently' do
22
+ expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
23
+ expect { node.start(context) }.not_to raise_error
24
+ end
29
25
 
30
- context 'given a log instruction' do
31
- before do
32
- allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination.new('Error', :warn))
33
- end
26
+ it 'logs the error by default' do
27
+ expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
28
+ node.start(context)
29
+ end
34
30
 
35
- it 'logs the error with the given cmd' do
36
- expect(EXEL.logger).to receive(:warn).with('JobTerminationError: Error')
37
- node.start(context)
38
- end
31
+ context 'given a log instruction' do
32
+ before do
33
+ allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination.new('Error', :warn))
34
+ end
35
+
36
+ it 'logs the error with the given cmd' do
37
+ expect(EXEL.logger).to receive(:warn).with('JobTerminationError: Error')
38
+ node.start(context)
39
39
  end
40
40
  end
41
41
  end
42
+ end
42
43
 
43
- describe '#run' do
44
- it 'raises an error if not implemented' do
45
- expect { TestNode.new(instruction).run(context) }.to raise_error 'EXEL::TestNode does not implement #process'
46
- end
44
+ describe '#run' do
45
+ it 'raises an error if not implemented' do
46
+ expect { TestNode.new(instruction).run(context) }.to raise_error 'TestNode does not implement #process'
47
47
  end
48
+ end
48
49
 
49
- describe '#add_child' do
50
- it 'adds the given node to its children' do
51
- root = ASTNode.new(instruction)
52
- child_node = ASTNode.new(instruction)
53
- child_node2 = ASTNode.new(instruction)
54
- root.add_child(child_node)
55
- root.add_child(child_node2)
50
+ describe '#add_child' do
51
+ it 'adds the given node to its children' do
52
+ root = EXEL::ASTNode.new(instruction)
53
+ child_node = EXEL::ASTNode.new(instruction)
54
+ child_node2 = EXEL::ASTNode.new(instruction)
55
+ root.add_child(child_node)
56
+ root.add_child(child_node2)
56
57
 
57
- expect(root.children).to eq([child_node, child_node2])
58
- end
58
+ expect(root.children).to eq([child_node, child_node2])
59
59
  end
60
60
  end
61
61
  end
@@ -1,109 +1,108 @@
1
1
  # frozen_string_literal: true
2
- module EXEL
3
- describe Context do
4
- subject(:context) { EXEL::Context.new(key1: '1', key2: 2) }
5
2
 
6
- it { is_expected.to be_a(Hash) }
3
+ describe EXEL::Context do
4
+ subject(:context) { EXEL::Context.new(key1: '1', key2: 2) }
7
5
 
8
- describe '#initialize' do
9
- it 'initializes with a hash' do
10
- expect(context[:key1]).to eq('1')
11
- expect(context[:key2]).to eq(2)
12
- expect(context[:key3]).to be_nil
13
- end
6
+ it { is_expected.to be_a(Hash) }
7
+
8
+ describe '#initialize' do
9
+ it 'initializes with a hash' do
10
+ expect(context[:key1]).to eq('1')
11
+ expect(context[:key2]).to eq(2)
12
+ expect(context[:key3]).to be_nil
14
13
  end
14
+ end
15
15
 
16
- describe '#deep_dup' do
17
- it 'returns a deep copy of itself' do
18
- context[:a] = {nested: []}
16
+ describe '#deep_dup' do
17
+ it 'returns a deep copy of itself' do
18
+ context[:a] = {nested: []}
19
19
 
20
- dup = context.deep_dup
21
- expect(context).to eq(dup)
22
- expect(context).not_to be_equal(dup)
20
+ dup = context.deep_dup
21
+ expect(context).to eq(dup)
22
+ expect(context).not_to be_equal(dup)
23
23
 
24
- dup[:a][:nested] << 1
25
- expect(context[:a][:nested]).to be_empty
26
- end
24
+ dup[:a][:nested] << 1
25
+ expect(context[:a][:nested]).to be_empty
27
26
  end
27
+ end
28
28
 
29
- describe '#serialize' do
30
- before { allow(Value).to receive(:upload) }
31
-
32
- it 'writes the serialized context to a file and upload it' do
33
- expect(Value).to receive(:remotize).with(context[:key1]).and_return('remote_value1')
34
- expect(Value).to receive(:remotize).with(context[:key2]).and_return('remote_value2')
29
+ describe '#serialize' do
30
+ before { allow(EXEL::Value).to receive(:upload) }
35
31
 
36
- expect(SecureRandom).to receive(:uuid).and_return('uuid')
32
+ it 'writes the serialized context to a file and upload it' do
33
+ expect(EXEL::Value).to receive(:remotize).with(context[:key1]).and_return('remote_value1')
34
+ expect(EXEL::Value).to receive(:remotize).with(context[:key2]).and_return('remote_value2')
37
35
 
38
- expect(Value).to receive(:remotize) do |file|
39
- expect(file.read).to eq(Marshal.dump(Context.new(key1: 'remote_value1', key2: 'remote_value2')))
40
- expect(file.path).to include('uuid')
41
- 'file_uri'
42
- end
36
+ expect(SecureRandom).to receive(:uuid).and_return('uuid')
43
37
 
44
- expect(context.serialize).to eq('file_uri')
38
+ expect(EXEL::Value).to receive(:remotize) do |file|
39
+ expect(file.read).to eq(Marshal.dump(EXEL::Context.new(key1: 'remote_value1', key2: 'remote_value2')))
40
+ expect(file.path).to include('uuid')
41
+ 'file_uri'
45
42
  end
46
43
 
47
- it 'does not mutate the current context' do
48
- allow(Value).to receive(:remotize).and_return('remote_value')
49
- original_table = context.dup
50
- context.serialize
51
- expect(context).to eq(original_table)
52
- end
44
+ expect(context.serialize).to eq('file_uri')
53
45
  end
54
46
 
55
- describe '.deserialize' do
56
- it 'deserializes a given uri' do
57
- file = StringIO.new(Marshal.dump(context))
58
- expect(Value).to receive(:localize).with('uri').and_return(file)
47
+ it 'does not mutate the current context' do
48
+ allow(EXEL::Value).to receive(:remotize).and_return('remote_value')
49
+ original_table = context.dup
50
+ context.serialize
51
+ expect(context).to eq(original_table)
52
+ end
53
+ end
59
54
 
60
- expect(Context.deserialize('uri')).to eq(context)
55
+ describe '.deserialize' do
56
+ it 'deserializes a given uri' do
57
+ file = StringIO.new(Marshal.dump(context))
58
+ expect(EXEL::Value).to receive(:localize).with('uri').and_return(file)
61
59
 
62
- expect(file).to be_closed
63
- end
60
+ expect(EXEL::Context.deserialize('uri')).to eq(context)
61
+
62
+ expect(file).to be_closed
64
63
  end
64
+ end
65
65
 
66
- shared_examples 'a reader method' do
67
- subject(:context) { EXEL::Context.new(key: 'value') }
66
+ shared_examples 'a reader method' do
67
+ subject(:context) { EXEL::Context.new(key: 'value') }
68
68
 
69
- it 'returns the value' do
70
- expect(context.send(method, :key)).to eq('value')
71
- end
69
+ it 'returns the value' do
70
+ expect(context.send(method, :key)).to eq('value')
71
+ end
72
72
 
73
- it 'localizes the returned value' do
74
- expect(Value).to receive(:localize).with('value').and_return('localized')
75
- expect(context.send(method, :key)).to eq('localized')
76
- end
73
+ it 'localizes the returned value' do
74
+ expect(EXEL::Value).to receive(:localize).with('value').and_return('localized')
75
+ expect(context.send(method, :key)).to eq('localized')
76
+ end
77
77
 
78
- it 'stores the localized value' do
79
- allow(Value).to receive(:localize).with('value').and_return('localized')
80
- context.send(method, :key)
81
- allow(Value).to receive(:localize).with('localized').and_return('localized')
82
- context.send(method, :key)
83
- end
78
+ it 'stores the localized value' do
79
+ allow(EXEL::Value).to receive(:localize).with('value').and_return('localized')
80
+ context.send(method, :key)
81
+ allow(EXEL::Value).to receive(:localize).with('localized').and_return('localized')
82
+ context.send(method, :key)
83
+ end
84
84
 
85
- it 'looks up deferred values' do
86
- # eq(context) as an argument matcher is necessary to prevent RSpec from calling fetch on the context, leading to
87
- # a stack overflow
88
- expect(DeferredContextValue).to receive(:resolve).with('value', eq(context)).and_return('resolved')
89
- expect(context.send(method, :key)).to eq('resolved')
90
- end
85
+ it 'looks up deferred values' do
86
+ # eq(context) as an argument matcher is necessary to prevent RSpec from calling fetch on the context, leading to
87
+ # a stack overflow
88
+ expect(EXEL::DeferredContextValue).to receive(:resolve).with('value', eq(context)).and_return('resolved')
89
+ expect(context.send(method, :key)).to eq('resolved')
91
90
  end
91
+ end
92
92
 
93
- describe '#[]' do
94
- it_behaves_like 'a reader method' do
95
- let(:method) { :[] }
96
- end
93
+ describe '#[]' do
94
+ it_behaves_like 'a reader method' do
95
+ let(:method) { :[] }
97
96
  end
97
+ end
98
98
 
99
- describe '#fetch' do
100
- it 'raises an exception if the key is not found' do
101
- expect { context.fetch(:unknown) }.to raise_error(KeyError)
102
- end
99
+ describe '#fetch' do
100
+ it 'raises an exception if the key is not found' do
101
+ expect { context.fetch(:unknown) }.to raise_error(KeyError)
102
+ end
103
103
 
104
- it_behaves_like 'a reader method' do
105
- let(:method) { :fetch }
106
- end
104
+ it_behaves_like 'a reader method' do
105
+ let(:method) { :fetch }
107
106
  end
108
107
  end
109
108
  end
@@ -1,66 +1,65 @@
1
1
  # frozen_string_literal: true
2
- module EXEL
3
- describe DeferredContextValue do
4
- subject(:deferred_value) { DeferredContextValue.new }
5
2
 
6
- describe '.resolve' do
7
- subject(:deferred_value) { DeferredContextValue.new[:key] }
3
+ describe EXEL::DeferredContextValue do
4
+ subject(:deferred_value) { EXEL::DeferredContextValue.new }
8
5
 
9
- context 'at the top level' do
10
- let(:context) { {key: 'value', deferred_value: deferred_value} }
6
+ describe '.resolve' do
7
+ subject(:deferred_value) { EXEL::DeferredContextValue.new[:key] }
11
8
 
12
- it 'returns the lookup value from the context' do
13
- expect(DeferredContextValue.resolve(context[:deferred_value], context)).to eq(context[:key])
14
- end
9
+ context 'at the top level' do
10
+ let(:context) { {key: 'value', deferred_value: deferred_value} }
11
+
12
+ it 'returns the lookup value from the context' do
13
+ expect(EXEL::DeferredContextValue.resolve(context[:deferred_value], context)).to eq(context[:key])
15
14
  end
15
+ end
16
16
 
17
- context 'in an array' do
18
- let(:context) { {key: 'value', array: [1, 2, deferred_value]} }
17
+ context 'in an array' do
18
+ let(:context) { {key: 'value', array: [1, 2, deferred_value]} }
19
19
 
20
- it 'returns the lookup value from the context' do
21
- expect(DeferredContextValue.resolve(context[:array], context)).to eq([1, 2, context[:key]])
22
- end
20
+ it 'returns the lookup value from the context' do
21
+ expect(EXEL::DeferredContextValue.resolve(context[:array], context)).to eq([1, 2, context[:key]])
23
22
  end
23
+ end
24
24
 
25
- context 'in a hash' do
26
- let(:context) { {key: 'value', hash: {hash_key: deferred_value}} }
25
+ context 'in a hash' do
26
+ let(:context) { {key: 'value', hash: {hash_key: deferred_value}} }
27
27
 
28
- it 'returns the lookup value from the context' do
29
- expect(DeferredContextValue.resolve(context[:hash], context)).to eq(hash_key: context[:key])
30
- end
28
+ it 'returns the lookup value from the context' do
29
+ expect(EXEL::DeferredContextValue.resolve(context[:hash], context)).to eq(hash_key: context[:key])
31
30
  end
31
+ end
32
32
 
33
- context 'in a hash nested in an array' do
34
- let(:context) { {key: 'value', nested: [{}, {hash_key: deferred_value}]} }
33
+ context 'in a hash nested in an array' do
34
+ let(:context) { {key: 'value', nested: [{}, {hash_key: deferred_value}]} }
35
35
 
36
- it 'looks up a deferred context value in a hash nested in an array' do
37
- expect(DeferredContextValue.resolve(context[:nested], context)).to eq([{}, {hash_key: context[:key]}])
38
- end
36
+ it 'looks up a deferred context value in a hash nested in an array' do
37
+ expect(EXEL::DeferredContextValue.resolve(context[:nested], context)).to eq([{}, {hash_key: context[:key]}])
39
38
  end
39
+ end
40
40
 
41
- context 'in an array nested in a hash' do
42
- let(:context) { {key: 'value', nested: {hash_key: [1, deferred_value]}} }
41
+ context 'in an array nested in a hash' do
42
+ let(:context) { {key: 'value', nested: {hash_key: [1, deferred_value]}} }
43
43
 
44
- it 'looks up a deferred context value in an array nested in a hash' do
45
- expect(DeferredContextValue.resolve(context[:nested], context)).to eq(hash_key: [1, context[:key]])
46
- end
44
+ it 'looks up a deferred context value in an array nested in a hash' do
45
+ expect(EXEL::DeferredContextValue.resolve(context[:nested], context)).to eq(hash_key: [1, context[:key]])
47
46
  end
48
47
  end
48
+ end
49
49
 
50
- describe '#[]' do
51
- it 'stores the given key in the keys attribute' do
52
- deferred_value[:top_level_key]['sub_key']
53
- expect(deferred_value.keys).to eq([:top_level_key, 'sub_key'])
54
- end
50
+ describe '#[]' do
51
+ it 'stores the given key in the keys attribute' do
52
+ deferred_value[:top_level_key]['sub_key']
53
+ expect(deferred_value.keys).to eq([:top_level_key, 'sub_key'])
55
54
  end
55
+ end
56
56
 
57
- describe '#get' do
58
- it 'looks up the value of the keys attribute in the passed-in context' do
59
- allow(deferred_value).to receive(:keys).and_return([:top_level_key, 'sub_key'])
60
- value = 'example_value'
61
- context = Context.new(top_level_key: {'sub_key' => value})
62
- expect(deferred_value.get(context)).to eq(value)
63
- end
57
+ describe '#get' do
58
+ it 'looks up the value of the keys attribute in the passed-in context' do
59
+ allow(deferred_value).to receive(:keys).and_return([:top_level_key, 'sub_key'])
60
+ value = 'example_value'
61
+ context = EXEL::Context.new(top_level_key: {'sub_key' => value})
62
+ expect(deferred_value.get(context)).to eq(value)
64
63
  end
65
64
  end
66
65
  end