exel 0.9.0 → 1.0.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 +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
|
+
[](https://badge.fury.io/rb/exel)
|
3
|
+
[](https://codeclimate.com/github/47colborne/exel)
|
4
|
+
[](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
|