exel 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +17 -0
- data/.rubocop_todo.yml +43 -0
- data/Guardfile +18 -11
- data/README.md +3 -0
- data/Rakefile +0 -1
- data/exel.gemspec +6 -5
- data/lib/exel/ast_node.rb +2 -2
- data/lib/exel/context.rb +16 -19
- data/lib/exel/deferred_context_value.rb +2 -2
- data/lib/exel/instruction.rb +2 -2
- data/lib/exel/job.rb +5 -5
- data/lib/exel/logging.rb +3 -3
- data/lib/exel/null_instruction.rb +1 -1
- data/lib/exel/processor_helper.rb +3 -5
- data/lib/exel/processors/async_processor.rb +3 -3
- data/lib/exel/providers/local_file_provider.rb +18 -0
- data/lib/exel/providers/threaded_async_provider.rb +13 -0
- data/lib/exel/{resource.rb → value.rb} +7 -11
- data/lib/exel/version.rb +1 -1
- data/lib/exel.rb +10 -1
- data/spec/exel/ast_node_spec.rb +3 -15
- data/spec/exel/context_spec.rb +40 -22
- data/spec/exel/job_spec.rb +8 -8
- data/spec/exel/logging_spec.rb +3 -3
- data/spec/exel/processors/async_processor_spec.rb +12 -4
- data/spec/exel/processors/split_processor_spec.rb +67 -67
- data/spec/exel/providers/local_async_provider_spec.rb +19 -0
- data/spec/exel/providers/local_file_provider_spec.rb +36 -0
- data/spec/exel/sequence_node_spec.rb +6 -13
- data/spec/exel/value_spec.rb +51 -0
- data/spec/exel_spec.rb +41 -0
- data/spec/spec_helper.rb +22 -3
- metadata +69 -40
- data/lib/exel/execution_worker.rb +0 -13
- data/lib/exel/handlers/s3_handler.rb +0 -43
- data/lib/exel/handlers/sidekiq_handler.rb +0 -21
- data/spec/exel/execution_worker_spec.rb +0 -13
- data/spec/exel/handlers/s3_handler_spec.rb +0 -49
- data/spec/exel/handlers/sidekiq_handler_spec.rb +0 -54
- data/spec/exel/resource_spec.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07a61d1ecdfd5d0d957caa0dd064631be8c3e546
|
4
|
+
data.tar.gz: 364c194ce4cb48938f1cc863b6bdf0a5b7e74e47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d004472bd8b4b3c6e496377c89cb261abcaa59fad5284009ecca65bee86dff428545af4ab27cd47ca74becbcdc9bb0a3d81962a67cf81f6437b5195a990cdf5f
|
7
|
+
data.tar.gz: debffc94d7f7fd53064f2b8f5317a68f3cbcaa4e961d19c6c2af630a31c39d79b4b42c111947e361a3f5a838853ff29d4a9814fb0892609da994f4c433517b85
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-rspec
|
3
|
+
- rubocop/rspec/focused
|
4
|
+
|
5
|
+
inherit_from: .rubocop_todo.yml
|
6
|
+
|
7
|
+
AllCops:
|
8
|
+
DisplayStyleGuide: true
|
9
|
+
|
10
|
+
Metrics/LineLength:
|
11
|
+
Max: 120
|
12
|
+
|
13
|
+
Style/SpaceInsideHashLiteralBraces:
|
14
|
+
EnforcedStyle: no_space
|
15
|
+
|
16
|
+
RSpec/DescribedClass:
|
17
|
+
Enabled: false
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2015-12-13 18:06:06 -0500 using RuboCop version 0.35.1.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 1
|
10
|
+
# Configuration parameters: CountComments.
|
11
|
+
Metrics/MethodLength:
|
12
|
+
Max: 11
|
13
|
+
|
14
|
+
# Offense count: 2
|
15
|
+
# Configuration parameters: CountComments.
|
16
|
+
Metrics/ModuleLength:
|
17
|
+
Max: 166
|
18
|
+
|
19
|
+
# Offense count: 65
|
20
|
+
# Configuration parameters: CustomTransform, IgnoredWords.
|
21
|
+
RSpec/ExampleWording:
|
22
|
+
Exclude:
|
23
|
+
- 'spec/exel/ast_node_spec.rb'
|
24
|
+
- 'spec/exel/context_spec.rb'
|
25
|
+
- 'spec/exel/deferred_context_value_spec.rb'
|
26
|
+
- 'spec/exel/handlers/s3_handler_spec.rb'
|
27
|
+
- 'spec/exel/instruction_node_spec.rb'
|
28
|
+
- 'spec/exel/instruction_spec.rb'
|
29
|
+
- 'spec/exel/job_spec.rb'
|
30
|
+
- 'spec/exel/logging_spec.rb'
|
31
|
+
- 'spec/exel/processors/split_processor_spec.rb'
|
32
|
+
- 'spec/exel/resource_spec.rb'
|
33
|
+
- 'spec/exel/sequence_node_spec.rb'
|
34
|
+
|
35
|
+
# Offense count: 1
|
36
|
+
RSpec/InstanceVariable:
|
37
|
+
Exclude:
|
38
|
+
- 'spec/exel/logging_spec.rb'
|
39
|
+
|
40
|
+
# Offense count: 18
|
41
|
+
# Configuration parameters: Exclude.
|
42
|
+
Style/Documentation:
|
43
|
+
Enabled: false
|
data/Guardfile
CHANGED
@@ -1,14 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
group :rspec_rubocop, halt_on_fail: true do
|
2
|
+
guard :rspec, cmd: 'bundle exec rspec' do
|
3
|
+
require 'guard/rspec/dsl'
|
4
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# RSpec files
|
7
|
+
rspec = dsl.rspec
|
8
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
9
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
10
|
+
watch(rspec.spec_files)
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
# Ruby files
|
13
|
+
ruby = dsl.ruby
|
14
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
15
|
+
end
|
16
|
+
|
17
|
+
guard :rubocop do
|
18
|
+
watch(/.+\.rb$/)
|
19
|
+
watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
|
20
|
+
end
|
14
21
|
end
|
data/README.md
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
# EXEL
|
2
|
+
[![Gem Version](https://badge.fury.io/rb/exel.svg)](https://badge.fury.io/rb/exel)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/47colborne/exel/badges/gpa.svg)](https://codeclimate.com/github/47colborne/exel)
|
4
|
+
[![Build Status](https://snap-ci.com/47colborne/exel/branch/master/build_image)](https://snap-ci.com/47colborne/exel/branch/master)
|
2
5
|
|
3
6
|
TODO: Write a gem description
|
4
7
|
|
data/Rakefile
CHANGED
data/exel.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = EXEL::VERSION
|
9
9
|
spec.authors = ['yroo']
|
10
10
|
spec.email = ['dev@yroo.com']
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
11
|
+
spec.summary = 'EXEL, the Elastic eXEcution Language'
|
12
|
+
spec.description = 'A DSL for defining jobs that can be run in a highly scalable manner'
|
13
13
|
spec.homepage = 'https://github.com/47colborne/exel'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
@@ -18,14 +18,15 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_dependency 'aws-sdk', '~> 2'
|
22
|
-
spec.add_dependency 'sidekiq', '~> 3'
|
23
|
-
|
24
21
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
25
22
|
spec.add_development_dependency 'rake', '~> 10'
|
26
23
|
spec.add_development_dependency 'rspec', '~> 3'
|
27
24
|
spec.add_development_dependency 'guard', '~> 2'
|
28
25
|
spec.add_development_dependency 'guard-rspec', '~> 4'
|
26
|
+
spec.add_development_dependency 'guard-rubocop', '~> 1'
|
29
27
|
spec.add_development_dependency 'terminal-notifier', '~> 1'
|
30
28
|
spec.add_development_dependency 'terminal-notifier-guard', '~> 1'
|
29
|
+
spec.add_development_dependency 'rubocop', '~> 0'
|
30
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1'
|
31
|
+
spec.add_development_dependency 'rubocop-rspec-focused', '~> 0'
|
31
32
|
end
|
data/lib/exel/ast_node.rb
CHANGED
@@ -2,7 +2,7 @@ module EXEL
|
|
2
2
|
class ASTNode
|
3
3
|
attr_reader :instruction, :children
|
4
4
|
|
5
|
-
def initialize(instruction, children=[])
|
5
|
+
def initialize(instruction, children = [])
|
6
6
|
@instruction = instruction
|
7
7
|
@children = children
|
8
8
|
end
|
@@ -12,7 +12,7 @@ module EXEL
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def run(_context)
|
15
|
-
|
15
|
+
fail "#{self.class} does not implement #process"
|
16
16
|
end
|
17
17
|
|
18
18
|
def add_child(node)
|
data/lib/exel/context.rb
CHANGED
@@ -4,26 +4,24 @@ module EXEL
|
|
4
4
|
class Context
|
5
5
|
attr_reader :table
|
6
6
|
|
7
|
-
def initialize(initial_context={})
|
7
|
+
def initialize(initial_context = {})
|
8
8
|
@table = initial_context
|
9
9
|
end
|
10
10
|
|
11
11
|
def serialize
|
12
|
-
remotized_table = @table.each_with_object({}) { |(key, value), acc| acc[key] = EXEL::
|
13
|
-
|
14
|
-
upload(file)
|
12
|
+
remotized_table = @table.each_with_object({}) { |(key, value), acc| acc[key] = EXEL::Value.remotize(value) }
|
13
|
+
EXEL::Value.remotize(serialized_context(remotized_table))
|
15
14
|
end
|
16
15
|
|
17
16
|
def self.deserialize(uri)
|
18
|
-
|
19
|
-
file = handler.download(uri)
|
17
|
+
file = EXEL::Value.localize(uri)
|
20
18
|
context = Marshal.load(file.read)
|
21
19
|
file.close
|
22
20
|
context
|
23
21
|
end
|
24
22
|
|
25
23
|
def [](key)
|
26
|
-
value = EXEL::
|
24
|
+
value = EXEL::Value.localize(@table[key])
|
27
25
|
value = get_deferred(value)
|
28
26
|
@table[key] = value
|
29
27
|
value
|
@@ -43,37 +41,36 @@ module EXEL
|
|
43
41
|
end
|
44
42
|
|
45
43
|
def ==(other)
|
46
|
-
other.
|
44
|
+
other.is_a?(EXEL::Context) && table == other.table
|
45
|
+
end
|
46
|
+
|
47
|
+
def include?(values)
|
48
|
+
@table.merge(values) == @table
|
47
49
|
end
|
48
50
|
|
49
51
|
private
|
50
52
|
|
51
|
-
def
|
53
|
+
def serialized_context(table)
|
52
54
|
file = Tempfile.new(SecureRandom.uuid, encoding: 'ascii-8bit')
|
53
55
|
file.write(Marshal.dump(Context.new(table)))
|
54
56
|
file.rewind
|
55
57
|
file
|
56
58
|
end
|
57
59
|
|
58
|
-
def upload(file)
|
59
|
-
handler = Handlers::S3Handler.new
|
60
|
-
handler.upload(file)
|
61
|
-
end
|
62
|
-
|
63
60
|
def get_deferred(value)
|
64
|
-
if
|
61
|
+
if deferred?(value)
|
65
62
|
value = value.get(self)
|
66
|
-
elsif value.
|
63
|
+
elsif value.is_a?(Array)
|
67
64
|
value.map! { |v| get_deferred(v) }
|
68
|
-
elsif value.
|
65
|
+
elsif value.is_a?(Hash)
|
69
66
|
value.each { |k, v| value[k] = get_deferred(v) }
|
70
67
|
end
|
71
68
|
|
72
69
|
value
|
73
70
|
end
|
74
71
|
|
75
|
-
def
|
76
|
-
value.
|
72
|
+
def deferred?(value)
|
73
|
+
value.is_a?(DeferredContextValue)
|
77
74
|
end
|
78
75
|
end
|
79
76
|
end
|
data/lib/exel/instruction.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module EXEL
|
2
|
-
class
|
2
|
+
class Instruction
|
3
3
|
attr_reader :name
|
4
4
|
|
5
|
-
def initialize(name, processor_class, args, subtree=nil)
|
5
|
+
def initialize(name, processor_class, args, subtree = nil)
|
6
6
|
@name = name
|
7
7
|
@processor_class = processor_class
|
8
8
|
@args = args || {}
|
data/lib/exel/job.rb
CHANGED
@@ -2,7 +2,7 @@ module EXEL
|
|
2
2
|
module Job
|
3
3
|
class << self
|
4
4
|
def define(job_name, &block)
|
5
|
-
|
5
|
+
fail "Job #{job_name.inspect} is already defined" unless registry[job_name].nil?
|
6
6
|
registry[job_name] = block
|
7
7
|
end
|
8
8
|
|
@@ -12,7 +12,7 @@ module EXEL
|
|
12
12
|
|
13
13
|
def run(dsl_code_or_name, context = {})
|
14
14
|
context = EXEL::Context.new(context) if context.is_a?(Hash)
|
15
|
-
(ast = parse(dsl_code_or_name)) ? ast.start(context) :
|
15
|
+
(ast = parse(dsl_code_or_name)) ? ast.start(context) : fail(%(Job "#{dsl_code_or_name}" not found))
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
@@ -49,11 +49,11 @@ module EXEL
|
|
49
49
|
add_instruction_node('process', processor_class, block, options)
|
50
50
|
end
|
51
51
|
|
52
|
-
def async(options={}, &block)
|
52
|
+
def async(options = {}, &block)
|
53
53
|
add_instruction_node('async', Processors::AsyncProcessor, block, options)
|
54
54
|
end
|
55
55
|
|
56
|
-
def split(options={}, &block)
|
56
|
+
def split(options = {}, &block)
|
57
57
|
add_instruction_node('split', Processors::SplitProcessor, block, options)
|
58
58
|
end
|
59
59
|
|
@@ -63,7 +63,7 @@ module EXEL
|
|
63
63
|
|
64
64
|
private
|
65
65
|
|
66
|
-
def add_instruction_node(name, processor, block, args={})
|
66
|
+
def add_instruction_node(name, processor, block, args = {})
|
67
67
|
sub_tree = block.nil? ? nil : Parser.parse(block)
|
68
68
|
instruction = EXEL::Instruction.new(name, processor, args, sub_tree)
|
69
69
|
node = sub_tree.nil? ? InstructionNode.new(instruction) : InstructionNode.new(instruction, [sub_tree])
|
data/lib/exel/logging.rb
CHANGED
@@ -15,11 +15,11 @@ module EXEL
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def self.log_filename
|
18
|
-
EXEL.configuration
|
18
|
+
EXEL.configuration.log_filename || '/dev/null'
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.log_level
|
22
|
-
level = EXEL.configuration
|
22
|
+
level = EXEL.configuration.log_level || DEFAULT_LEVEL
|
23
23
|
Logger.const_get(level.to_s.upcase)
|
24
24
|
end
|
25
25
|
|
@@ -27,4 +27,4 @@ module EXEL
|
|
27
27
|
@logger = logger || Logger.new('/dev/null')
|
28
28
|
end
|
29
29
|
end
|
30
|
-
end
|
30
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module EXEL
|
2
2
|
module ProcessorHelper
|
3
|
-
|
4
3
|
# Helper Methods
|
5
4
|
|
6
5
|
def tag(*tags)
|
@@ -12,7 +11,7 @@ module EXEL
|
|
12
11
|
end
|
13
12
|
|
14
13
|
def file_size_in_mb(file)
|
15
|
-
|
14
|
+
format('%.2f MB', file.size.to_f / 1_024_000)
|
16
15
|
end
|
17
16
|
|
18
17
|
# Logging Helpers
|
@@ -38,10 +37,10 @@ module EXEL
|
|
38
37
|
end
|
39
38
|
|
40
39
|
def log_transaction(message = '')
|
41
|
-
transaction_start_time = Time.now
|
40
|
+
transaction_start_time = Time.now
|
42
41
|
log_info "Started at #{transaction_start_time}"
|
43
42
|
yield(transaction_start_time)
|
44
|
-
transaction_end_time = Time.now
|
43
|
+
transaction_end_time = Time.now
|
45
44
|
log_info "Finished in #{(transaction_end_time - transaction_start_time).to_i} seconds #{message}"
|
46
45
|
end
|
47
46
|
|
@@ -62,6 +61,5 @@ module EXEL
|
|
62
61
|
time_to_sleep = duration.second.to_f - elapsed_time
|
63
62
|
sleep(time_to_sleep) if time_to_sleep > 0
|
64
63
|
end
|
65
|
-
|
66
64
|
end
|
67
65
|
end
|
@@ -4,18 +4,18 @@ module EXEL
|
|
4
4
|
module Processors
|
5
5
|
class AsyncProcessor
|
6
6
|
include EXEL::ProcessorHelper
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :provider
|
8
8
|
|
9
9
|
def initialize(context)
|
10
10
|
@context = context
|
11
|
-
@
|
11
|
+
@provider = EXEL.async_provider.new(context)
|
12
12
|
|
13
13
|
log_prefix_with '[AsyncProcessor]'
|
14
14
|
end
|
15
15
|
|
16
16
|
def process(block)
|
17
17
|
log_process do
|
18
|
-
@
|
18
|
+
@provider.do_async(block)
|
19
19
|
log_info 'call to async completed'
|
20
20
|
end
|
21
21
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module EXEL
|
2
|
+
module Providers
|
3
|
+
class LocalFileProvider
|
4
|
+
def upload(file)
|
5
|
+
"file://#{file.path}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def download(uri)
|
9
|
+
fail 'URI must begin with "file://"' unless uri.start_with? 'file://'
|
10
|
+
File.open(uri.split('file://').last)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.remote?(uri)
|
14
|
+
uri =~ %r{file://}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
module EXEL
|
2
|
-
|
2
|
+
module Value
|
3
3
|
def self.remotize(value)
|
4
4
|
file?(value) ? upload(value) : value
|
5
5
|
end
|
6
6
|
|
7
7
|
def self.localize(value)
|
8
|
-
|
8
|
+
remote?(value) ? download(value) : value
|
9
9
|
end
|
10
10
|
|
11
11
|
class << self
|
@@ -15,20 +15,16 @@ module EXEL
|
|
15
15
|
value.is_a?(File) || value.is_a?(Tempfile)
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
18
|
+
def upload(file)
|
19
|
+
EXEL.remote_provider.new.upload(file)
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
|
22
|
+
def remote?(value)
|
23
|
+
EXEL.remote_provider.remote?(value)
|
24
24
|
end
|
25
25
|
|
26
26
|
def download(uri)
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
def upload(file)
|
31
|
-
Handlers::S3Handler.new.upload(file)
|
27
|
+
EXEL.remote_provider.new.download(uri)
|
32
28
|
end
|
33
29
|
end
|
34
30
|
end
|
data/lib/exel/version.rb
CHANGED
data/lib/exel.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'exel/version'
|
2
2
|
require 'exel/logging'
|
3
|
+
require 'ostruct'
|
3
4
|
|
4
5
|
module EXEL
|
5
6
|
def self.logger
|
@@ -11,13 +12,21 @@ module EXEL
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def self.configuration
|
14
|
-
@config ||=
|
15
|
+
@config ||= OpenStruct.new
|
15
16
|
end
|
16
17
|
|
17
18
|
def self.configure
|
18
19
|
yield configuration
|
19
20
|
end
|
20
21
|
|
22
|
+
def self.async_provider
|
23
|
+
configuration.async_provider || Providers::ThreadedAsyncProvider
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.remote_provider
|
27
|
+
configuration.remote_provider || Providers::LocalFileProvider
|
28
|
+
end
|
29
|
+
|
21
30
|
root = File.expand_path('../..', __FILE__)
|
22
31
|
Dir[File.join(root, 'lib/exel/**/*.rb')].each { |file| require file }
|
23
32
|
end
|
data/spec/exel/ast_node_spec.rb
CHANGED
@@ -1,23 +1,14 @@
|
|
1
1
|
module EXEL
|
2
2
|
describe ASTNode do
|
3
|
-
let(:context) {
|
4
|
-
|
5
|
-
def build_tree
|
6
|
-
@node_3 = ASTNode.new(instruction)
|
7
|
-
@node_4 = ASTNode.new(instruction)
|
8
|
-
@node_2 = ASTNode.new(instruction, [@node_3, @node_4])
|
9
|
-
@node_5 = ASTNode.new(instruction)
|
10
|
-
@node_1 = ASTNode.new(instruction, [@node_2, @node_5])
|
11
|
-
end
|
3
|
+
let(:context) { instance_double(EXEL::Context) }
|
12
4
|
|
13
5
|
def instruction
|
14
6
|
instance_double(Instruction, execute: nil)
|
15
7
|
end
|
16
8
|
|
17
|
-
|
18
|
-
class TestNode < ASTNode
|
19
|
-
end
|
9
|
+
TestNode = Class.new(ASTNode)
|
20
10
|
|
11
|
+
describe '#start' do
|
21
12
|
context 'when an JobTermination error bubbles up' do
|
22
13
|
it 'should ensure the process fails silently' do
|
23
14
|
node = TestNode.new(instruction)
|
@@ -29,9 +20,6 @@ module EXEL
|
|
29
20
|
end
|
30
21
|
|
31
22
|
describe '#run' do
|
32
|
-
class TestNode < ASTNode;
|
33
|
-
end
|
34
|
-
|
35
23
|
it 'should raise an error if not implemented' do
|
36
24
|
expect { TestNode.new(instruction).run(context) }.to raise_error 'EXEL::TestNode does not implement #process'
|
37
25
|
end
|