exel 1.1.0 → 1.2.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.
- checksums.yaml +4 -4
 - data/.rubocop.yml +1 -0
 - data/.rubocop_todo.yml +10 -16
 - data/Gemfile +2 -0
 - data/README.md +5 -2
 - data/exel.gemspec +2 -1
 - data/lib/exel/ast_node.rb +2 -1
 - data/lib/exel/context.rb +45 -42
 - data/lib/exel/deferred_context_value.rb +35 -0
 - data/lib/exel/error/job_termination.rb +3 -5
 - data/lib/exel/events.rb +26 -0
 - data/lib/exel/instruction.rb +2 -4
 - data/lib/exel/instruction_node.rb +1 -0
 - data/lib/exel/job.rb +32 -10
 - data/lib/exel/listen_instruction.rb +17 -0
 - data/lib/exel/null_instruction.rb +1 -0
 - data/lib/exel/old_context.rb +109 -0
 - data/lib/exel/processor_helper.rb +1 -4
 - data/lib/exel/processors/async_processor.rb +1 -0
 - data/lib/exel/processors/run_processor.rb +3 -0
 - data/lib/exel/processors/split_processor.rb +12 -2
 - data/lib/exel/providers/local_file_provider.rb +3 -1
 - data/lib/exel/providers/threaded_async_provider.rb +1 -0
 - data/lib/exel/sequence_node.rb +1 -0
 - data/lib/exel/value.rb +1 -0
 - data/lib/exel/version.rb +1 -1
 - data/lib/exel.rb +20 -1
 - data/spec/exel/ast_node_spec.rb +4 -4
 - data/spec/exel/context_spec.rb +35 -109
 - data/spec/exel/deferred_context_value_spec.rb +51 -7
 - data/spec/exel/events_spec.rb +89 -0
 - data/spec/exel/instruction_node_spec.rb +3 -3
 - data/spec/exel/instruction_spec.rb +9 -9
 - data/spec/exel/job_spec.rb +23 -13
 - data/spec/exel/listen_instruction_spec.rb +14 -0
 - data/spec/exel/logging_spec.rb +3 -3
 - data/spec/exel/processors/split_processor_spec.rb +14 -6
 - data/spec/exel/sequence_node_spec.rb +1 -1
 - data/spec/exel_spec.rb +7 -0
 - data/spec/fixtures/sample.csv +501 -0
 - data/spec/integration/integration_spec.rb +51 -0
 - data/spec/spec_helper.rb +17 -1
 - data/spec/support/integration_test_classes.rb +44 -0
 - metadata +32 -5
 
| 
         @@ -4,18 +4,28 @@ require_relative '../processor_helper' 
     | 
|
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module EXEL
         
     | 
| 
       6 
6 
     | 
    
         
             
              module Processors
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Implements the +split+ instruction. Used to concurrently process a large file by splitting it into small chunks to
         
     | 
| 
      
 8 
     | 
    
         
            +
                # be separately processed.
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                # =Supported Context Options
         
     | 
| 
      
 11 
     | 
    
         
            +
                # * +:delete_resource+ Defaults to true, can be set to false to preserve the original resource. Otherwise, it will
         
     | 
| 
      
 12 
     | 
    
         
            +
                #   be deleted when splitting is complete
         
     | 
| 
      
 13 
     | 
    
         
            +
                # * +:chunk_size+ Set to specify the number of lines that each chunk should contain
         
     | 
| 
       7 
14 
     | 
    
         
             
                class SplitProcessor
         
     | 
| 
       8 
15 
     | 
    
         
             
                  include EXEL::ProcessorHelper
         
     | 
| 
       9 
16 
     | 
    
         | 
| 
       10 
17 
     | 
    
         
             
                  attr_accessor :file_name, :block
         
     | 
| 
       11 
18 
     | 
    
         | 
| 
      
 19 
     | 
    
         
            +
                  # Number of lines to include in each chunk. Can be overridden by setting :chunk_size in the context
         
     | 
| 
       12 
20 
     | 
    
         
             
                  DEFAULT_CHUNK_SIZE = 1000
         
     | 
| 
       13 
21 
     | 
    
         | 
| 
      
 22 
     | 
    
         
            +
                  # The context must contain a CSV File object in context[:resource]
         
     | 
| 
       14 
23 
     | 
    
         
             
                  def initialize(context)
         
     | 
| 
       15 
24 
     | 
    
         
             
                    @buffer = []
         
     | 
| 
       16 
25 
     | 
    
         
             
                    @tempfile_count = 0
         
     | 
| 
       17 
26 
     | 
    
         
             
                    @context = context
         
     | 
| 
       18 
27 
     | 
    
         
             
                    @file = context[:resource]
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @context[:delete_resource] = true if @context[:delete_resource].nil?
         
     | 
| 
       19 
29 
     | 
    
         | 
| 
       20 
30 
     | 
    
         
             
                    log_prefix_with '[SplitProcessor]'
         
     | 
| 
       21 
31 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -73,7 +83,7 @@ module EXEL 
     | 
|
| 
       73 
83 
     | 
    
         
             
                  end
         
     | 
| 
       74 
84 
     | 
    
         | 
| 
       75 
85 
     | 
    
         
             
                  def chunk_size
         
     | 
| 
       76 
     | 
    
         
            -
                    DEFAULT_CHUNK_SIZE
         
     | 
| 
      
 86 
     | 
    
         
            +
                    @context[:chunk_size] || DEFAULT_CHUNK_SIZE
         
     | 
| 
       77 
87 
     | 
    
         
             
                  end
         
     | 
| 
       78 
88 
     | 
    
         | 
| 
       79 
89 
     | 
    
         
             
                  def chunk_filename
         
     | 
| 
         @@ -87,7 +97,7 @@ module EXEL 
     | 
|
| 
       87 
97 
     | 
    
         | 
| 
       88 
98 
     | 
    
         
             
                  def finish(callback)
         
     | 
| 
       89 
99 
     | 
    
         
             
                    process_line(:eof, callback)
         
     | 
| 
       90 
     | 
    
         
            -
                    File.delete(@file.path)
         
     | 
| 
      
 100 
     | 
    
         
            +
                    File.delete(@file.path) if @context[:delete_resource]
         
     | 
| 
       91 
101 
     | 
    
         
             
                  end
         
     | 
| 
       92 
102 
     | 
    
         
             
                end
         
     | 
| 
       93 
103 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,12 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module EXEL
         
     | 
| 
       2 
2 
     | 
    
         
             
              module Providers
         
     | 
| 
      
 3 
     | 
    
         
            +
                # The default remote provider. Doesn't actually upload and download files to and from remote storage, but rather
         
     | 
| 
      
 4 
     | 
    
         
            +
                # just works with local files.
         
     | 
| 
       3 
5 
     | 
    
         
             
                class LocalFileProvider
         
     | 
| 
       4 
6 
     | 
    
         
             
                  def upload(file)
         
     | 
| 
       5 
7 
     | 
    
         
             
                    "file://#{file.path}"
         
     | 
| 
       6 
8 
     | 
    
         
             
                  end
         
     | 
| 
       7 
9 
     | 
    
         | 
| 
       8 
10 
     | 
    
         
             
                  def download(uri)
         
     | 
| 
       9 
     | 
    
         
            -
                     
     | 
| 
      
 11 
     | 
    
         
            +
                    raise 'URI must begin with "file://"' unless uri.start_with? 'file://'
         
     | 
| 
       10 
12 
     | 
    
         
             
                    File.open(uri.split('file://').last)
         
     | 
| 
       11 
13 
     | 
    
         
             
                  end
         
     | 
| 
       12 
14 
     | 
    
         | 
    
        data/lib/exel/sequence_node.rb
    CHANGED
    
    
    
        data/lib/exel/value.rb
    CHANGED
    
    
    
        data/lib/exel/version.rb
    CHANGED
    
    
    
        data/lib/exel.rb
    CHANGED
    
    | 
         @@ -2,31 +2,50 @@ require 'exel/version' 
     | 
|
| 
       2 
2 
     | 
    
         
             
            require 'exel/logging'
         
     | 
| 
       3 
3 
     | 
    
         
             
            require 'ostruct'
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
      
 5 
     | 
    
         
            +
            # Provides methods to configure EXEL
         
     | 
| 
       5 
6 
     | 
    
         
             
            module EXEL
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @return The currently set logger
         
     | 
| 
       6 
8 
     | 
    
         
             
              def self.logger
         
     | 
| 
       7 
9 
     | 
    
         
             
                EXEL::Logging.logger
         
     | 
| 
       8 
10 
     | 
    
         
             
              end
         
     | 
| 
       9 
11 
     | 
    
         | 
| 
      
 12 
     | 
    
         
            +
              # Sets the logger to be used.
         
     | 
| 
      
 13 
     | 
    
         
            +
              #
         
     | 
| 
      
 14 
     | 
    
         
            +
              # @param [Logger] The logger to set. Must comply with the Ruby Logger interface
         
     | 
| 
       10 
15 
     | 
    
         
             
              def self.logger=(logger)
         
     | 
| 
       11 
16 
     | 
    
         
             
                EXEL::Logging.logger = logger
         
     | 
| 
       12 
17 
     | 
    
         
             
              end
         
     | 
| 
       13 
18 
     | 
    
         | 
| 
      
 19 
     | 
    
         
            +
              # @return The current configuration
         
     | 
| 
       14 
20 
     | 
    
         
             
              def self.configuration
         
     | 
| 
       15 
21 
     | 
    
         
             
                @config ||= OpenStruct.new
         
     | 
| 
       16 
22 
     | 
    
         
             
              end
         
     | 
| 
       17 
23 
     | 
    
         | 
| 
      
 24 
     | 
    
         
            +
              # Yields the configuration object to the given block. Configuration can include:
         
     | 
| 
      
 25 
     | 
    
         
            +
              # * +async_provider+ Set an async provider. Defaults to EXEL::Providers::ThreadedAsyncProvider
         
     | 
| 
      
 26 
     | 
    
         
            +
              # * +remote_provider+ Set a remote provider. Defaults to EXEL::Providers::LocalFileProvider
         
     | 
| 
      
 27 
     | 
    
         
            +
              # * Any configuration required by the async/remote providers
         
     | 
| 
      
 28 
     | 
    
         
            +
              #
         
     | 
| 
      
 29 
     | 
    
         
            +
              # Typically, async_provider and remote_provider will be automatically set upon requiring those gems.
         
     | 
| 
      
 30 
     | 
    
         
            +
              #
         
     | 
| 
      
 31 
     | 
    
         
            +
              # Example:
         
     | 
| 
      
 32 
     | 
    
         
            +
              #   EXEL.configure do |config|
         
     | 
| 
      
 33 
     | 
    
         
            +
              #     config.s3_bucket = 'my_bucket'
         
     | 
| 
      
 34 
     | 
    
         
            +
              #   end
         
     | 
| 
       18 
35 
     | 
    
         
             
              def self.configure
         
     | 
| 
       19 
36 
     | 
    
         
             
                yield configuration
         
     | 
| 
       20 
37 
     | 
    
         
             
              end
         
     | 
| 
       21 
38 
     | 
    
         | 
| 
      
 39 
     | 
    
         
            +
              # @return The currently configured async provider. Defaults to EXEL::Providers::ThreadedAsyncProvider
         
     | 
| 
       22 
40 
     | 
    
         
             
              def self.async_provider
         
     | 
| 
       23 
41 
     | 
    
         
             
                configuration.async_provider || Providers::ThreadedAsyncProvider
         
     | 
| 
       24 
42 
     | 
    
         
             
              end
         
     | 
| 
       25 
43 
     | 
    
         | 
| 
      
 44 
     | 
    
         
            +
              # @return The currently configured remote provider. Defaults to EXEL::Providers::LocalFileProvider
         
     | 
| 
       26 
45 
     | 
    
         
             
              def self.remote_provider
         
     | 
| 
       27 
46 
     | 
    
         
             
                configuration.remote_provider || Providers::LocalFileProvider
         
     | 
| 
       28 
47 
     | 
    
         
             
              end
         
     | 
| 
       29 
48 
     | 
    
         | 
| 
       30 
49 
     | 
    
         
             
              root = File.expand_path('../..', __FILE__)
         
     | 
| 
       31 
     | 
    
         
            -
              Dir[File.join(root, 'lib/exel/**/*.rb')].each { |file| require file }
         
     | 
| 
      
 50 
     | 
    
         
            +
              Dir[File.join(root, 'lib/exel/**/*.rb')].reject { |file| file.include?('old_context') }.each { |file| require file }
         
     | 
| 
       32 
51 
     | 
    
         
             
            end
         
     | 
    
        data/spec/exel/ast_node_spec.rb
    CHANGED
    
    | 
         @@ -10,23 +10,23 @@ module EXEL 
     | 
|
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
                describe '#start' do
         
     | 
| 
       12 
12 
     | 
    
         
             
                  context 'when an JobTermination error bubbles up' do
         
     | 
| 
       13 
     | 
    
         
            -
                    it ' 
     | 
| 
      
 13 
     | 
    
         
            +
                    it 'ensures the process fails silently' do
         
     | 
| 
       14 
14 
     | 
    
         
             
                      node = TestNode.new(instruction)
         
     | 
| 
       15 
15 
     | 
    
         
             
                      allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination, 'Error')
         
     | 
| 
       16 
16 
     | 
    
         
             
                      expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
         
     | 
| 
       17 
     | 
    
         
            -
                      expect { node.start(context) }. 
     | 
| 
      
 17 
     | 
    
         
            +
                      expect { node.start(context) }.not_to raise_error
         
     | 
| 
       18 
18 
     | 
    
         
             
                    end
         
     | 
| 
       19 
19 
     | 
    
         
             
                  end
         
     | 
| 
       20 
20 
     | 
    
         
             
                end
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
                describe '#run' do
         
     | 
| 
       23 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 23 
     | 
    
         
            +
                  it 'raises an error if not implemented' do
         
     | 
| 
       24 
24 
     | 
    
         
             
                    expect { TestNode.new(instruction).run(context) }.to raise_error 'EXEL::TestNode does not implement #process'
         
     | 
| 
       25 
25 
     | 
    
         
             
                  end
         
     | 
| 
       26 
26 
     | 
    
         
             
                end
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
28 
     | 
    
         
             
                describe '#add_child' do
         
     | 
| 
       29 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 29 
     | 
    
         
            +
                  it 'adds the given node to its children' do
         
     | 
| 
       30 
30 
     | 
    
         
             
                    root = ASTNode.new(instruction)
         
     | 
| 
       31 
31 
     | 
    
         
             
                    child_node = ASTNode.new(instruction)
         
     | 
| 
       32 
32 
     | 
    
         
             
                    child_node2 = ASTNode.new(instruction)
         
     | 
    
        data/spec/exel/context_spec.rb
    CHANGED
    
    | 
         @@ -2,10 +2,13 @@ module EXEL 
     | 
|
| 
       2 
2 
     | 
    
         
             
              describe Context do
         
     | 
| 
       3 
3 
     | 
    
         
             
                subject(:context) { EXEL::Context.new(key1: '1', key2: 2) }
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
      
 5 
     | 
    
         
            +
                it { is_expected.to be_a(Hash) }
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
       5 
7 
     | 
    
         
             
                describe '#initialize' do
         
     | 
| 
       6 
     | 
    
         
            -
                  it ' 
     | 
| 
       7 
     | 
    
         
            -
                    expect(context 
     | 
| 
       8 
     | 
    
         
            -
                    expect(context 
     | 
| 
      
 8 
     | 
    
         
            +
                  it 'initializes with a hash' do
         
     | 
| 
      
 9 
     | 
    
         
            +
                    expect(context[:key1]).to eq('1')
         
     | 
| 
      
 10 
     | 
    
         
            +
                    expect(context[:key2]).to eq(2)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    expect(context[:key3]).to be_nil
         
     | 
| 
       9 
12 
     | 
    
         
             
                  end
         
     | 
| 
       10 
13 
     | 
    
         
             
                end
         
     | 
| 
       11 
14 
     | 
    
         | 
| 
         @@ -15,7 +18,7 @@ module EXEL 
     | 
|
| 
       15 
18 
     | 
    
         | 
| 
       16 
19 
     | 
    
         
             
                    dup = context.deep_dup
         
     | 
| 
       17 
20 
     | 
    
         
             
                    expect(context).to eq(dup)
         
     | 
| 
       18 
     | 
    
         
            -
                    expect(context). 
     | 
| 
      
 21 
     | 
    
         
            +
                    expect(context).not_to be_equal(dup)
         
     | 
| 
       19 
22 
     | 
    
         | 
| 
       20 
23 
     | 
    
         
             
                    dup[:a][:nested] << 1
         
     | 
| 
       21 
24 
     | 
    
         
             
                    expect(context[:a][:nested]).to be_empty
         
     | 
| 
         @@ -25,7 +28,7 @@ module EXEL 
     | 
|
| 
       25 
28 
     | 
    
         
             
                describe '#serialize' do
         
     | 
| 
       26 
29 
     | 
    
         
             
                  before { allow(Value).to receive(:upload) }
         
     | 
| 
       27 
30 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 31 
     | 
    
         
            +
                  it 'writes the serialized context to a file and upload it' do
         
     | 
| 
       29 
32 
     | 
    
         
             
                    expect(Value).to receive(:remotize).with(context[:key1]).and_return('remote_value1')
         
     | 
| 
       30 
33 
     | 
    
         
             
                    expect(Value).to receive(:remotize).with(context[:key2]).and_return('remote_value2')
         
     | 
| 
       31 
34 
     | 
    
         | 
| 
         @@ -40,15 +43,16 @@ module EXEL 
     | 
|
| 
       40 
43 
     | 
    
         
             
                    expect(context.serialize).to eq('file_uri')
         
     | 
| 
       41 
44 
     | 
    
         
             
                  end
         
     | 
| 
       42 
45 
     | 
    
         | 
| 
       43 
     | 
    
         
            -
                  it ' 
     | 
| 
       44 
     | 
    
         
            -
                     
     | 
| 
      
 46 
     | 
    
         
            +
                  it 'does not mutate the current context' do
         
     | 
| 
      
 47 
     | 
    
         
            +
                    allow(Value).to receive(:remotize).and_return('remote_value')
         
     | 
| 
      
 48 
     | 
    
         
            +
                    original_table = context.dup
         
     | 
| 
       45 
49 
     | 
    
         
             
                    context.serialize
         
     | 
| 
       46 
     | 
    
         
            -
                    expect(context 
     | 
| 
      
 50 
     | 
    
         
            +
                    expect(context).to eq(original_table)
         
     | 
| 
       47 
51 
     | 
    
         
             
                  end
         
     | 
| 
       48 
52 
     | 
    
         
             
                end
         
     | 
| 
       49 
53 
     | 
    
         | 
| 
       50 
54 
     | 
    
         
             
                describe '.deserialize' do
         
     | 
| 
       51 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 55 
     | 
    
         
            +
                  it 'deserializes a given uri' do
         
     | 
| 
       52 
56 
     | 
    
         
             
                    file = StringIO.new(Marshal.dump(context))
         
     | 
| 
       53 
57 
     | 
    
         
             
                    expect(Value).to receive(:localize).with('uri').and_return(file)
         
     | 
| 
       54 
58 
     | 
    
         | 
| 
         @@ -58,124 +62,46 @@ module EXEL 
     | 
|
| 
       58 
62 
     | 
    
         
             
                  end
         
     | 
| 
       59 
63 
     | 
    
         
             
                end
         
     | 
| 
       60 
64 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
                 
     | 
| 
      
 65 
     | 
    
         
            +
                shared_examples 'a reader method' do
         
     | 
| 
       62 
66 
     | 
    
         
             
                  subject(:context) { EXEL::Context.new(key: 'value') }
         
     | 
| 
       63 
67 
     | 
    
         | 
| 
       64 
     | 
    
         
            -
                  it ' 
     | 
| 
       65 
     | 
    
         
            -
                    expect(context 
     | 
| 
      
 68 
     | 
    
         
            +
                  it 'returns the value' do
         
     | 
| 
      
 69 
     | 
    
         
            +
                    expect(context.send(method, :key)).to eq('value')
         
     | 
| 
       66 
70 
     | 
    
         
             
                  end
         
     | 
| 
       67 
71 
     | 
    
         | 
| 
       68 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 72 
     | 
    
         
            +
                  it 'localizes the returned value' do
         
     | 
| 
       69 
73 
     | 
    
         
             
                    expect(Value).to receive(:localize).with('value').and_return('localized')
         
     | 
| 
       70 
     | 
    
         
            -
                    expect(context 
     | 
| 
      
 74 
     | 
    
         
            +
                    expect(context.send(method, :key)).to eq('localized')
         
     | 
| 
       71 
75 
     | 
    
         
             
                  end
         
     | 
| 
       72 
76 
     | 
    
         | 
| 
       73 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 77 
     | 
    
         
            +
                  it 'stores the localized value' do
         
     | 
| 
       74 
78 
     | 
    
         
             
                    allow(Value).to receive(:localize).with('value').and_return('localized')
         
     | 
| 
       75 
     | 
    
         
            -
                    context 
     | 
| 
       76 
     | 
    
         
            -
                     
     | 
| 
      
 79 
     | 
    
         
            +
                    context.send(method, :key)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    allow(Value).to receive(:localize).with('localized').and_return('localized')
         
     | 
| 
      
 81 
     | 
    
         
            +
                    context.send(method, :key)
         
     | 
| 
       77 
82 
     | 
    
         
             
                  end
         
     | 
| 
       78 
83 
     | 
    
         | 
| 
       79 
     | 
    
         
            -
                   
     | 
| 
       80 
     | 
    
         
            -
                    context  
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
       84 
     | 
    
         
            -
                        expect(context[:deferred_value]).to eq(context[:key])
         
     | 
| 
       85 
     | 
    
         
            -
                      end
         
     | 
| 
       86 
     | 
    
         
            -
                    end
         
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
                    context 'in an array' do
         
     | 
| 
       89 
     | 
    
         
            -
                      it 'should return the lookup value from the context' do
         
     | 
| 
       90 
     | 
    
         
            -
                        deferred_context_value = DeferredContextValue.new[:key]
         
     | 
| 
       91 
     | 
    
         
            -
                        context[:array] = [1, 2, deferred_context_value]
         
     | 
| 
       92 
     | 
    
         
            -
                        expect(context[:array]).to eq([1, 2, context[:key]])
         
     | 
| 
       93 
     | 
    
         
            -
                      end
         
     | 
| 
       94 
     | 
    
         
            -
                    end
         
     | 
| 
       95 
     | 
    
         
            -
             
     | 
| 
       96 
     | 
    
         
            -
                    context 'in a hash' do
         
     | 
| 
       97 
     | 
    
         
            -
                      it 'should return the lookup value from the context' do
         
     | 
| 
       98 
     | 
    
         
            -
                        deferred_context_value = DeferredContextValue.new[:key]
         
     | 
| 
       99 
     | 
    
         
            -
                        context[:hash] = {hash_key: deferred_context_value}
         
     | 
| 
       100 
     | 
    
         
            -
                        expect(context[:hash]).to eq(hash_key: context[:key])
         
     | 
| 
       101 
     | 
    
         
            -
                      end
         
     | 
| 
       102 
     | 
    
         
            -
                    end
         
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
                    context 'in nested arrays and hashes' do
         
     | 
| 
       105 
     | 
    
         
            -
                      it 'should lookup a deferred context value in a hash nested in an array' do
         
     | 
| 
       106 
     | 
    
         
            -
                        deferred_context_value = DeferredContextValue.new[:key]
         
     | 
| 
       107 
     | 
    
         
            -
                        context[:nested] = [{}, {hash_key: deferred_context_value}]
         
     | 
| 
       108 
     | 
    
         
            -
                        expect(context[:nested]).to eq([{}, {hash_key: context[:key]}])
         
     | 
| 
       109 
     | 
    
         
            -
                      end
         
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
       111 
     | 
    
         
            -
                      it 'should lookup a deferred context value in an array nested in a hash' do
         
     | 
| 
       112 
     | 
    
         
            -
                        deferred_context_value = DeferredContextValue.new[:key]
         
     | 
| 
       113 
     | 
    
         
            -
                        context[:nested] = {hash_key: [1, deferred_context_value]}
         
     | 
| 
       114 
     | 
    
         
            -
                        expect(context[:nested]).to eq(hash_key: [1, context[:key]])
         
     | 
| 
       115 
     | 
    
         
            -
                      end
         
     | 
| 
       116 
     | 
    
         
            -
                    end
         
     | 
| 
       117 
     | 
    
         
            -
                  end
         
     | 
| 
       118 
     | 
    
         
            -
                end
         
     | 
| 
       119 
     | 
    
         
            -
             
     | 
| 
       120 
     | 
    
         
            -
                describe '#[]=' do
         
     | 
| 
       121 
     | 
    
         
            -
                  it 'should add the key/value pair to table' do
         
     | 
| 
       122 
     | 
    
         
            -
                    context[:new_key] = 'new_value'
         
     | 
| 
       123 
     | 
    
         
            -
                    expect(context.table[:new_key]).to eq('new_value')
         
     | 
| 
       124 
     | 
    
         
            -
                  end
         
     | 
| 
       125 
     | 
    
         
            -
                end
         
     | 
| 
       126 
     | 
    
         
            -
             
     | 
| 
       127 
     | 
    
         
            -
                describe '#delete' do
         
     | 
| 
       128 
     | 
    
         
            -
                  it 'should delete the key/value pair from the table' do
         
     | 
| 
       129 
     | 
    
         
            -
                    context[:key] = 'value'
         
     | 
| 
       130 
     | 
    
         
            -
                    context[:key2] = 'value2'
         
     | 
| 
       131 
     | 
    
         
            -
                    context.delete(:key)
         
     | 
| 
       132 
     | 
    
         
            -
                    expect(context.table.keys).to_not include(:key)
         
     | 
| 
       133 
     | 
    
         
            -
                    expect(context.table.keys).to include(:key2)
         
     | 
| 
      
 84 
     | 
    
         
            +
                  it 'looks up deferred values' do
         
     | 
| 
      
 85 
     | 
    
         
            +
                    # eq(context) as an argument matcher is necessary to prevent RSpec from calling fetch on the context, leading to
         
     | 
| 
      
 86 
     | 
    
         
            +
                    # a stack overflow
         
     | 
| 
      
 87 
     | 
    
         
            +
                    expect(DeferredContextValue).to receive(:resolve).with('value', eq(context)).and_return('resolved')
         
     | 
| 
      
 88 
     | 
    
         
            +
                    expect(context.send(method, :key)).to eq('resolved')
         
     | 
| 
       134 
89 
     | 
    
         
             
                  end
         
     | 
| 
       135 
90 
     | 
    
         
             
                end
         
     | 
| 
       136 
91 
     | 
    
         | 
| 
       137 
     | 
    
         
            -
                describe '# 
     | 
| 
       138 
     | 
    
         
            -
                   
     | 
| 
       139 
     | 
    
         
            -
                     
     | 
| 
       140 
     | 
    
         
            -
                    context.table[:existing] = 'existing'
         
     | 
| 
       141 
     | 
    
         
            -
             
     | 
| 
       142 
     | 
    
         
            -
                    context.merge!(overwrite: 'changed', new: 'new')
         
     | 
| 
       143 
     | 
    
         
            -
             
     | 
| 
       144 
     | 
    
         
            -
                    expect(context.table[:overwrite]).to eq('changed')
         
     | 
| 
       145 
     | 
    
         
            -
                    expect(context.table[:existing]).to eq('existing')
         
     | 
| 
       146 
     | 
    
         
            -
                    expect(context.table[:new]).to eq('new')
         
     | 
| 
       147 
     | 
    
         
            -
                  end
         
     | 
| 
       148 
     | 
    
         
            -
             
     | 
| 
       149 
     | 
    
         
            -
                  it 'should return itself' do
         
     | 
| 
       150 
     | 
    
         
            -
                    expect(context.merge!(key: 'value')).to eq(context)
         
     | 
| 
      
 92 
     | 
    
         
            +
                describe '#[]' do
         
     | 
| 
      
 93 
     | 
    
         
            +
                  it_behaves_like 'a reader method' do
         
     | 
| 
      
 94 
     | 
    
         
            +
                    let(:method) { :[] }
         
     | 
| 
       151 
95 
     | 
    
         
             
                  end
         
     | 
| 
       152 
96 
     | 
    
         
             
                end
         
     | 
| 
       153 
97 
     | 
    
         | 
| 
       154 
     | 
    
         
            -
                describe ' 
     | 
| 
       155 
     | 
    
         
            -
                  it  
     | 
| 
       156 
     | 
    
         
            -
             
     | 
| 
       157 
     | 
    
         
            -
                  it { is_expected.to eq(context) }
         
     | 
| 
       158 
     | 
    
         
            -
             
     | 
| 
       159 
     | 
    
         
            -
                  it { is_expected.to_not eq(42) }
         
     | 
| 
       160 
     | 
    
         
            -
             
     | 
| 
       161 
     | 
    
         
            -
                  it { is_expected.to_not eq(Context.new(other_key: 'value')) }
         
     | 
| 
       162 
     | 
    
         
            -
             
     | 
| 
       163 
     | 
    
         
            -
                  it { is_expected.to eq(context.dup) }
         
     | 
| 
       164 
     | 
    
         
            -
                end
         
     | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
       166 
     | 
    
         
            -
                describe 'include?' do
         
     | 
| 
       167 
     | 
    
         
            -
                  subject(:context) { EXEL::Context.new(key1: 1, key2: 2, key3: 3) }
         
     | 
| 
       168 
     | 
    
         
            -
             
     | 
| 
       169 
     | 
    
         
            -
                  context 'context contains all key value pairs' do
         
     | 
| 
       170 
     | 
    
         
            -
                    it 'should return true' do
         
     | 
| 
       171 
     | 
    
         
            -
                      expect(context).to include(key1: 1, key2: 2)
         
     | 
| 
       172 
     | 
    
         
            -
                    end
         
     | 
| 
      
 98 
     | 
    
         
            +
                describe '#fetch' do
         
     | 
| 
      
 99 
     | 
    
         
            +
                  it 'raises an exception if the key is not found' do
         
     | 
| 
      
 100 
     | 
    
         
            +
                    expect { context.fetch(:unknown) }.to raise_error(KeyError)
         
     | 
| 
       173 
101 
     | 
    
         
             
                  end
         
     | 
| 
       174 
102 
     | 
    
         | 
| 
       175 
     | 
    
         
            -
                   
     | 
| 
       176 
     | 
    
         
            -
                     
     | 
| 
       177 
     | 
    
         
            -
                      expect(context).not_to include(foo: 'bar', key2: 2)
         
     | 
| 
       178 
     | 
    
         
            -
                    end
         
     | 
| 
      
 103 
     | 
    
         
            +
                  it_behaves_like 'a reader method' do
         
     | 
| 
      
 104 
     | 
    
         
            +
                    let(:method) { :fetch }
         
     | 
| 
       179 
105 
     | 
    
         
             
                  end
         
     | 
| 
       180 
106 
     | 
    
         
             
                end
         
     | 
| 
       181 
107 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,20 +1,64 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module EXEL
         
     | 
| 
       2 
2 
     | 
    
         
             
              describe DeferredContextValue do
         
     | 
| 
       3 
     | 
    
         
            -
                subject(: 
     | 
| 
      
 3 
     | 
    
         
            +
                subject(:deferred_value) { DeferredContextValue.new }
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                describe '.resolve' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                  subject(:deferred_value) { DeferredContextValue.new[:key] }
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  context 'at the top level' do
         
     | 
| 
      
 9 
     | 
    
         
            +
                    let(:context) { {key: 'value', deferred_value: deferred_value} }
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                    it 'returns the lookup value from the context' do
         
     | 
| 
      
 12 
     | 
    
         
            +
                      expect(DeferredContextValue.resolve(context[:deferred_value], context)).to eq(context[:key])
         
     | 
| 
      
 13 
     | 
    
         
            +
                    end
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  context 'in an array' do
         
     | 
| 
      
 17 
     | 
    
         
            +
                    let(:context) { {key: 'value', array: [1, 2, deferred_value]} }
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    it 'returns the lookup value from the context' do
         
     | 
| 
      
 20 
     | 
    
         
            +
                      expect(DeferredContextValue.resolve(context[:array], context)).to eq([1, 2, context[:key]])
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  context 'in a hash' do
         
     | 
| 
      
 25 
     | 
    
         
            +
                    let(:context) { {key: 'value', hash: {hash_key: deferred_value}} }
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    it 'returns the lookup value from the context' do
         
     | 
| 
      
 28 
     | 
    
         
            +
                      expect(DeferredContextValue.resolve(context[:hash], context)).to eq(hash_key: context[:key])
         
     | 
| 
      
 29 
     | 
    
         
            +
                    end
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  context 'in a hash nested in an array' do
         
     | 
| 
      
 33 
     | 
    
         
            +
                    let(:context) { {key: 'value', nested: [{}, {hash_key: deferred_value}]} }
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                    it 'looks up a deferred context value in a hash nested in an array' do
         
     | 
| 
      
 36 
     | 
    
         
            +
                      expect(DeferredContextValue.resolve(context[:nested], context)).to eq([{}, {hash_key: context[:key]}])
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  context 'in an array nested in a hash' do
         
     | 
| 
      
 41 
     | 
    
         
            +
                    let(:context) { {key: 'value', nested: {hash_key: [1, deferred_value]}} }
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    it 'looks up a deferred context value in an array nested in a hash' do
         
     | 
| 
      
 44 
     | 
    
         
            +
                      expect(DeferredContextValue.resolve(context[:nested], context)).to eq(hash_key: [1, context[:key]])
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
       4 
48 
     | 
    
         | 
| 
       5 
49 
     | 
    
         
             
                describe '#[]' do
         
     | 
| 
       6 
     | 
    
         
            -
                  it ' 
     | 
| 
       7 
     | 
    
         
            -
                     
     | 
| 
       8 
     | 
    
         
            -
                    expect( 
     | 
| 
      
 50 
     | 
    
         
            +
                  it 'stores the given key in the keys attribute' do
         
     | 
| 
      
 51 
     | 
    
         
            +
                    deferred_value[:top_level_key]['sub_key']
         
     | 
| 
      
 52 
     | 
    
         
            +
                    expect(deferred_value.keys).to eq([:top_level_key, 'sub_key'])
         
     | 
| 
       9 
53 
     | 
    
         
             
                  end
         
     | 
| 
       10 
54 
     | 
    
         
             
                end
         
     | 
| 
       11 
55 
     | 
    
         | 
| 
       12 
56 
     | 
    
         
             
                describe '#get' do
         
     | 
| 
       13 
     | 
    
         
            -
                  it ' 
     | 
| 
       14 
     | 
    
         
            -
                    allow( 
     | 
| 
      
 57 
     | 
    
         
            +
                  it 'looks up the value of the keys attribute in the passed-in context' do
         
     | 
| 
      
 58 
     | 
    
         
            +
                    allow(deferred_value).to receive(:keys).and_return([:top_level_key, 'sub_key'])
         
     | 
| 
       15 
59 
     | 
    
         
             
                    value = 'example_value'
         
     | 
| 
       16 
60 
     | 
    
         
             
                    context = Context.new(top_level_key: {'sub_key' => value})
         
     | 
| 
       17 
     | 
    
         
            -
                    expect( 
     | 
| 
      
 61 
     | 
    
         
            +
                    expect(deferred_value.get(context)).to eq(value)
         
     | 
| 
       18 
62 
     | 
    
         
             
                  end
         
     | 
| 
       19 
63 
     | 
    
         
             
                end
         
     | 
| 
       20 
64 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,89 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module EXEL
         
     | 
| 
      
 2 
     | 
    
         
            +
              describe Events do
         
     | 
| 
      
 3 
     | 
    
         
            +
                class EventTest
         
     | 
| 
      
 4 
     | 
    
         
            +
                  include Events
         
     | 
| 
      
 5 
     | 
    
         
            +
                end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                subject(:events) { EventTest.new }
         
     | 
| 
      
 8 
     | 
    
         
            +
                let(:event_listener) { double(:event_listener) }
         
     | 
| 
      
 9 
     | 
    
         
            +
                let(:context) { EXEL::Context.new(Events::LISTENERS_KEY => {event: [event_listener]}) }
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                describe '#register_listener' do
         
     | 
| 
      
 12 
     | 
    
         
            +
                  context 'when no listeners have been defined' do
         
     | 
| 
      
 13 
     | 
    
         
            +
                    let(:context) { EXEL::Context.new }
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    it 'adds a new listener to the context' do
         
     | 
| 
      
 16 
     | 
    
         
            +
                      events.register_listener(context, :event, event_listener)
         
     | 
| 
      
 17 
     | 
    
         
            +
                      expect(context[Events::LISTENERS_KEY].fetch(:event)).to contain_exactly(event_listener)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  it 'registers multiple listeners for the same event' do
         
     | 
| 
      
 22 
     | 
    
         
            +
                    new_listener = double(:event_listener2)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    events.register_listener(context, :event, new_listener)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    expect(context[Events::LISTENERS_KEY].fetch(:event)).to contain_exactly(event_listener, new_listener)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                describe '#trigger' do
         
     | 
| 
      
 29 
     | 
    
         
            +
                  let(:context) { EXEL::Context.new }
         
     | 
| 
      
 30 
     | 
    
         
            +
                  let(:data) { {foo: 1} }
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  before { allow(events).to receive(:context).and_return(context) }
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  context 'when no events have been registered' do
         
     | 
| 
      
 35 
     | 
    
         
            +
                    it 'does not trigger anything' do
         
     | 
| 
      
 36 
     | 
    
         
            +
                      expect(event_listener).not_to receive(:event)
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                      events.trigger(:event, data)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  context 'with a single listener registered for the event' do
         
     | 
| 
      
 43 
     | 
    
         
            +
                    before do
         
     | 
| 
      
 44 
     | 
    
         
            +
                      events.register_listener(context, :event, event_listener)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                    it 'calls the listener with the context and event data' do
         
     | 
| 
      
 48 
     | 
    
         
            +
                      expect(event_listener).to receive(:event).with(context, data)
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                      events.trigger(:event, data)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    it 'passes an empty hash if no data was given' do
         
     | 
| 
      
 54 
     | 
    
         
            +
                      expect(event_listener).to receive(:event).with(context, {})
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                      events.trigger(:event)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    end
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                  context 'with no listeners registered for the event' do
         
     | 
| 
      
 61 
     | 
    
         
            +
                    before do
         
     | 
| 
      
 62 
     | 
    
         
            +
                      events.register_listener(context, :other_event, event_listener)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                    it 'does not trigger anything' do
         
     | 
| 
      
 66 
     | 
    
         
            +
                      expect(event_listener).not_to receive(:event)
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                      events.trigger(:event, data)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    end
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  context 'with multiple listeners registered for the event' do
         
     | 
| 
      
 73 
     | 
    
         
            +
                    let(:event_listener2) { double(:event_listener2) }
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                    before do
         
     | 
| 
      
 76 
     | 
    
         
            +
                      events.register_listener(context, :event, event_listener)
         
     | 
| 
      
 77 
     | 
    
         
            +
                      events.register_listener(context, :event, event_listener2)
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    it 'calls each listener with the context and event data' do
         
     | 
| 
      
 81 
     | 
    
         
            +
                      expect(event_listener).to receive(:event).with(context, data)
         
     | 
| 
      
 82 
     | 
    
         
            +
                      expect(event_listener2).to receive(:event).with(context, data)
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                      events.trigger(:event, data)
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
      
 88 
     | 
    
         
            +
              end
         
     | 
| 
      
 89 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -8,13 +8,13 @@ module EXEL 
     | 
|
| 
       8 
8 
     | 
    
         
             
                it { is_expected.to be_kind_of(ASTNode) }
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
                describe '#run' do
         
     | 
| 
       11 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 11 
     | 
    
         
            +
                  it 'only executes the instruction' do
         
     | 
| 
       12 
12 
     | 
    
         
             
                    expect(instruction).to receive(:execute).with(context).once
         
     | 
| 
       13 
13 
     | 
    
         
             
                    node.run(context)
         
     | 
| 
       14 
14 
     | 
    
         
             
                  end
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                  it ' 
     | 
| 
       17 
     | 
    
         
            -
                    expect(child). 
     | 
| 
      
 16 
     | 
    
         
            +
                  it 'does not run it`s children' do
         
     | 
| 
      
 17 
     | 
    
         
            +
                    expect(child).not_to receive(:run)
         
     | 
| 
       18 
18 
     | 
    
         
             
                    node.run(context)
         
     | 
| 
       19 
19 
     | 
    
         
             
                  end
         
     | 
| 
       20 
20 
     | 
    
         
             
                end
         
     | 
| 
         @@ -1,20 +1,20 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module EXEL
         
     | 
| 
       2 
2 
     | 
    
         
             
              describe Instruction do
         
     | 
| 
       3 
     | 
    
         
            -
                subject(:instruction) { EXEL::Instruction.new( 
     | 
| 
      
 3 
     | 
    
         
            +
                subject(:instruction) { EXEL::Instruction.new(processor_class, args) }
         
     | 
| 
       4 
4 
     | 
    
         
             
                let(:processor_class) { double(:processor_class, new: processor_instance) }
         
     | 
| 
       5 
5 
     | 
    
         
             
                let(:processor_instance) { double(:processor_instance, process: nil) }
         
     | 
| 
       6 
6 
     | 
    
         
             
                let(:args) { {arg1: 'arg_value1', arg2: {}} }
         
     | 
| 
       7 
7 
     | 
    
         
             
                let(:context) { {context_key: 'context_value'} }
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
                describe '# 
     | 
| 
       10 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 9 
     | 
    
         
            +
                describe '#execute' do
         
     | 
| 
      
 10 
     | 
    
         
            +
                  it 'calls process on an instance of the processor class' do
         
     | 
| 
       11 
11 
     | 
    
         
             
                    expect(processor_class).to receive(:new).and_return(processor_instance)
         
     | 
| 
       12 
12 
     | 
    
         
             
                    expect(processor_instance).to receive(:process)
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                    instruction.execute(context)
         
     | 
| 
       15 
15 
     | 
    
         
             
                  end
         
     | 
| 
       16 
16 
     | 
    
         | 
| 
       17 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 17 
     | 
    
         
            +
                  it 'does not pass a copy of the context' do
         
     | 
| 
       18 
18 
     | 
    
         
             
                    allow(processor_class).to receive(:new) do |context_arg|
         
     | 
| 
       19 
19 
     | 
    
         
             
                      expect(context_arg).to be(context)
         
     | 
| 
       20 
20 
     | 
    
         
             
                      processor_instance
         
     | 
| 
         @@ -23,13 +23,13 @@ module EXEL 
     | 
|
| 
       23 
23 
     | 
    
         
             
                    instruction.execute(context)
         
     | 
| 
       24 
24 
     | 
    
         
             
                  end
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
                  it ' 
     | 
| 
      
 26 
     | 
    
         
            +
                  it 'adds args to the context' do
         
     | 
| 
       27 
27 
     | 
    
         
             
                    instruction.execute(context)
         
     | 
| 
       28 
28 
     | 
    
         
             
                    expect(context.keys).to include(*args.keys)
         
     | 
| 
       29 
29 
     | 
    
         
             
                  end
         
     | 
| 
       30 
30 
     | 
    
         | 
| 
       31 
31 
     | 
    
         
             
                  context 'with args' do
         
     | 
| 
       32 
     | 
    
         
            -
                    it ' 
     | 
| 
      
 32 
     | 
    
         
            +
                    it 'passes the args to the processor' do
         
     | 
| 
       33 
33 
     | 
    
         
             
                      expect(processor_class).to receive(:new).with(hash_including(args))
         
     | 
| 
       34 
34 
     | 
    
         
             
                      instruction.execute(context)
         
     | 
| 
       35 
35 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -38,7 +38,7 @@ module EXEL 
     | 
|
| 
       38 
38 
     | 
    
         
             
                  context 'without args' do
         
     | 
| 
       39 
39 
     | 
    
         
             
                    let(:args) { nil }
         
     | 
| 
       40 
40 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
                    it ' 
     | 
| 
      
 41 
     | 
    
         
            +
                    it 'passes only the context to the processor' do
         
     | 
| 
       42 
42 
     | 
    
         
             
                      expect(processor_class).to receive(:new).with(context)
         
     | 
| 
       43 
43 
     | 
    
         
             
                      instruction.execute(context)
         
     | 
| 
       44 
44 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -46,9 +46,9 @@ module EXEL 
     | 
|
| 
       46 
46 
     | 
    
         | 
| 
       47 
47 
     | 
    
         
             
                  context 'with a subtree' do
         
     | 
| 
       48 
48 
     | 
    
         
             
                    let(:subtree) { double(:subtree) }
         
     | 
| 
       49 
     | 
    
         
            -
                    subject(:instruction) { EXEL::Instruction.new( 
     | 
| 
      
 49 
     | 
    
         
            +
                    subject(:instruction) { EXEL::Instruction.new(processor_class, args, subtree) }
         
     | 
| 
       50 
50 
     | 
    
         | 
| 
       51 
     | 
    
         
            -
                    it ' 
     | 
| 
      
 51 
     | 
    
         
            +
                    it 'passes the subtree to the processor' do
         
     | 
| 
       52 
52 
     | 
    
         
             
                      expect(processor_instance).to receive(:process).with(subtree)
         
     | 
| 
       53 
53 
     | 
    
         
             
                      instruction.execute(context)
         
     | 
| 
       54 
54 
     | 
    
         
             
                    end
         
     |