contained_mr 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
1
+ # @see {ContainedMr::Runner}
2
+ class ContainedMr::Mock::Runner
3
+ # @return {Hash<String, Object>} the options passed to the constructor
4
+ attr_reader :_container_options
5
+ # @return {Hash<String, Object>} the time limit passed to the constructor
6
+ attr_reader :_time_limit
7
+ # @return {Hash<String, Object>} the output path passed to the constructor
8
+ attr_reader :_output_path
9
+
10
+ # @return {Boolean} true if {#perform} was called
11
+ def performed?
12
+ @performed
13
+ end
14
+
15
+ # @return {Boolean} true if {#destroy!} was called
16
+ def destroyed?
17
+ @destroyed
18
+ end
19
+
20
+ include ContainedMr::RunnerLogic
21
+
22
+ # @see {ContainedMr::Runner#initialize}
23
+ def initialize(container_options, time_limit, output_path)
24
+ @_container_options = container_options
25
+ @_time_limit = time_limit
26
+ @_output_path = output_path
27
+
28
+ @container_id = nil
29
+ @started_at = @ended_at = nil
30
+ @status_code = nil
31
+ @timed_out = nil
32
+ @stdout = @stderr = nil
33
+ @output = nil
34
+
35
+ @performed = false
36
+ @destroyed = false
37
+ end
38
+
39
+ # @see {ContainedMr::Runner#perform}
40
+ def perform
41
+ @performed = true
42
+ self
43
+ end
44
+
45
+ # @see {ContainedMr::Runner#destroy!}
46
+ def destroy!
47
+ @destroyed = true
48
+ self
49
+ end
50
+
51
+ # Sets the container execution data returned by the mock.
52
+ #
53
+ # @param {Hash<Symbol, Object>} attributes values describing the result of
54
+ # running the job
55
+ # @return {ContainedMr::Runner} self
56
+ def _mock_set(attributes)
57
+ @started_at = attributes[:started_at]
58
+ @ended_at = attributes[:ended_at]
59
+ @status_code = attributes[:status_code]
60
+ @timed_out = attributes[:timed_out]
61
+ @stdout = attributes[:stdout]
62
+ @stderr = attributes[:stderr]
63
+ @output = attributes[:output]
64
+ self
65
+ end
66
+
67
+ # Convenience method for looking up an ulimit in the container options.
68
+ #
69
+ # @param {String} name the ulimit's name, such as 'cpu' or 'rss'
70
+ # @return {Number} the ulimit's hard and soft value, or nil if the ulimit was
71
+ # not found
72
+ # @raise {RuntimeError} if the ulimit's hard and soft values don't match
73
+ def _ulimit(name)
74
+ return nil unless ulimits = @_container_options['Ulimits']
75
+
76
+ ulimits.each do |ulimit|
77
+ if ulimit['Name'] == name
78
+ if ulimit['Hard'] != ulimit['Soft']
79
+ raise RuntimeError, "Hard/soft ulimit mismatch for #{name}"
80
+ end
81
+ return ulimit['Hard']
82
+ end
83
+ end
84
+ nil
85
+ end
86
+ end
@@ -0,0 +1,62 @@
1
+ # @see {ContainedMr::Template}
2
+ class ContainedMr::Mock::Template
3
+ # @return {Hash<String, Object>} YAML-parsed mapreduced.yml
4
+ attr_reader :_definition
5
+
6
+ # @return {Hash<String, Symbol|String>} maps file names in the template .zip
7
+ # to their contents, and maps directory entries to the :directory symbol
8
+ attr_reader :_zip_contents
9
+
10
+ include ContainedMr::TemplateLogic
11
+
12
+ # @return {Boolean} true if {#destroy!} was called
13
+ def destroyed?
14
+ @destroyed
15
+ end
16
+
17
+ # @see {ContainedMr::Template#initialize}
18
+ def initialize(name_prefix, id, zip_io)
19
+ @name_prefix = name_prefix
20
+ @id = id
21
+ @image_id = 'mock-template-image-id'
22
+ @item_count = nil
23
+ @_definition = nil
24
+
25
+ @destroyed = false
26
+ @_zip_contents = {}
27
+
28
+ process_zip zip_io
29
+ end
30
+
31
+ # @see {ContainedMr::Template#destroy!}
32
+ def destroy!
33
+ @destroyed = true
34
+ self
35
+ end
36
+
37
+ # @see {ContainedMr::Template#new_job}
38
+ def job_class
39
+ ContainedMr::Mock::Job
40
+ end
41
+
42
+ # Reads the template .zip and parses the definition.
43
+ def process_zip(zip_io)
44
+ # TODO(pwnall): zip_io.read -> zip_io after rubyzip releases 1.1.8
45
+ Zip::File.open_buffer zip_io.read do |zip|
46
+ zip.each do |zip_entry|
47
+ file_name = zip_entry.name
48
+ if zip_entry.directory?
49
+ @_zip_contents[file_name] = :directory
50
+ elsif zip_entry.file?
51
+ if file_name == 'mapreduced.yml'
52
+ read_definition zip_entry.get_input_stream
53
+ next
54
+ end
55
+ @_zip_contents[file_name] = zip_entry.get_input_stream.read
56
+ end
57
+ end
58
+ end
59
+ @_zip_contents.freeze
60
+ end
61
+ private :process_zip
62
+ end
@@ -0,0 +1,3 @@
1
+ # Namespace for mock classes that facilitate writing tests against ContainedMr.
2
+ module ContainedMr::Mock
3
+ end
@@ -0,0 +1,24 @@
1
+ # Namespace and factory for templates.
2
+ module ContainedMr
3
+ # Sets up the template and builds its Docker base image.
4
+ #
5
+ # This method should be used instead of calling {ContainedMr::Template.new}
6
+ # directly. This way, tests can stub {ContainedMr.template_class} to have it
7
+ # return {ContainedMr::Mock::Template}.
8
+ #
9
+ # @param {String} name_prefix prepended to Docker objects, for identification
10
+ # purposes
11
+ # @param {String} id the template's unique identifier
12
+ # @param {String} zip_io IO implementation that produces the template .zip
13
+ def self.new_template(name_prefix, id, zip_io)
14
+ template_class.new name_prefix, id, zip_io
15
+ end
16
+
17
+ # The class instantiated by {ContainedMr.new_template}.
18
+ #
19
+ # @return {Class} by default {ContainedMr::Template}; tests should stub this
20
+ # method and have it return {ContainedMr::Mock::Template}
21
+ def self.template_class
22
+ ContainedMr::Template
23
+ end
24
+ end
@@ -5,15 +5,20 @@ require 'docker'
5
5
 
6
6
  # Handles running a single mapper or reducer.
7
7
  class ContainedMr::Runner
8
- attr_reader :container_id
9
- attr_reader :started_at, :ended_at, :status_code, :timed_out
10
- attr_reader :stdout, :stderr, :output
8
+ include ContainedMr::RunnerLogic
11
9
 
12
- # C
10
+ # Initialize a runner.
11
+ #
12
+ # @param {Hash<String, Object>} container_options docker container creation
13
+ # options, passed to {Docker::Container.create} without modification
14
+ # @param {Number} time_limit maximum number of seconds that the runner's
15
+ # container is allowed to execute before being terminated
16
+ # @param {String} output_path the location of the file inside the container
17
+ # whose output will be saved
13
18
  def initialize(container_options, time_limit, output_path)
14
- @container_options = container_options
15
- @time_limit = time_limit
16
- @output_path = output_path
19
+ @_container_options = container_options
20
+ @_time_limit = time_limit
21
+ @_output_path = output_path
17
22
 
18
23
  @container_id = nil
19
24
  @started_at = @ended_at = nil
@@ -23,8 +28,9 @@ class ContainedMr::Runner
23
28
  @output = nil
24
29
  end
25
30
 
26
-
27
31
  # Performs a full mapper / reducer step.
32
+ #
33
+ # @return {ContainedMr::Runner} self
28
34
  def perform
29
35
  container = create
30
36
  @container_id = container.id
@@ -32,20 +38,29 @@ class ContainedMr::Runner
32
38
  execute container
33
39
  fetch_console_output container
34
40
  fetch_file_output container
35
- destroy container
36
- self
41
+ destroy! container
37
42
  end
38
43
 
39
- # @return {Number} the container's running time, in seconds
40
- def ran_for
41
- @started_at && @ended_at && (@ended_at - @started_at)
44
+ # Removes the container used to run a mapper / reducer.
45
+ #
46
+ # @param {Docker::Container} container the mapper / reducer's container; if
47
+ # not supplied, an extra Docker API query is performed to obtain the
48
+ # container object
49
+ # @return {ContainedMr::Runner} self
50
+ def destroy!(container = nil)
51
+ unless @container_id.nil?
52
+ container ||= Docker::Container.get @container_id
53
+ container.delete force: true
54
+ @container_id = nil
55
+ end
56
+ self
42
57
  end
43
58
 
44
59
  # Creates a container for running a mapper or reducer.
45
60
  #
46
61
  # @return {Docker::Container} newly created container
47
62
  def create
48
- Docker::Container.create @container_options
63
+ Docker::Container.create @_container_options
49
64
  end
50
65
  private :create
51
66
 
@@ -56,7 +71,7 @@ class ContainedMr::Runner
56
71
  container.start
57
72
  @started_at = Time.now
58
73
  begin
59
- wait_status = container.wait @time_limit
74
+ wait_status = container.wait @_time_limit
60
75
  @status_code = wait_status['StatusCode']
61
76
  @timed_out = false
62
77
  rescue Docker::Error::TimeoutError
@@ -107,19 +122,11 @@ class ContainedMr::Runner
107
122
  # @return {IO} an IO implementation that sources the .tar data
108
123
  def fetch_tar_output(container)
109
124
  tar_buffer = StringIO.new
110
- container.copy @output_path do |data|
125
+ container.copy @_output_path do |data|
111
126
  tar_buffer << data
112
127
  end
113
128
  tar_buffer.rewind
114
129
  tar_buffer
115
130
  end
116
131
  private :fetch_tar_output
117
-
118
- # Removes the container used to run a mapper / reducer.
119
- #
120
- # @param {Docker::Container} container the mapper / reducer's container
121
- def destroy(container)
122
- container.delete
123
- @container_id = nil
124
- end
125
132
  end
@@ -0,0 +1,38 @@
1
+ # Logic shared by {ContainedMr::Runner} and {ContainedMr::Mock::Runner}.
2
+ module ContainedMr::RunnerLogic
3
+ # @return {Time} the time when the mapper or reducer starts running
4
+ attr_reader :started_at
5
+ # @return {Time} the time when the mapper or reducer stops running or is killed
6
+ attr_reader :ended_at
7
+ # @return {Number} the time
8
+ attr_reader :status_code
9
+ # @return {Boolean} true if the mapper or reducer was terminated due to
10
+ # running for too long
11
+ attr_reader :timed_out
12
+
13
+ # @return {String} the data written by the mapper or reducer to stdout
14
+ attr_reader :stdout
15
+ # @return {String} the data written by the mapper or reducer to stderr
16
+ attr_reader :stderr
17
+ # @return {String} the contents of the file
18
+ attr_reader :output
19
+
20
+ # @return {String} the unique ID of the Docker container used to run the
21
+ # mapper / reducer; this is nil
22
+ attr_reader :container_id
23
+
24
+ # @return {Number} the container's running time, in seconds
25
+ def ran_for
26
+ started_at && ended_at && (ended_at - started_at)
27
+ end
28
+
29
+ # The information written to the mapper status files given to the reducer.
30
+ #
31
+ # This is saved in files named 1.json, 2.json, ... provided to the reducer.
32
+ #
33
+ # @return {Hash<Symbol, Object>} JSON-compatible representation of the
34
+ # runner's information
35
+ def json_file
36
+ { ran_for: ran_for, exit_code: status_code, timed_out: timed_out }
37
+ end
38
+ end
@@ -7,20 +7,15 @@ require 'zip'
7
7
 
8
8
  # A template is used to spawn multiple Map-Reduce jobs.
9
9
  class ContainedMr::Template
10
- attr_reader :name_prefix, :item_count, :image_id
10
+ include ContainedMr::TemplateLogic
11
11
 
12
- # Sets up the template and builds its Docker base image.
13
- #
14
- # @param {String} name_prefix prepended to Docker objects, for identification
15
- # purposes
16
- # @param {String} id the job's unique identifier
17
- # @param {String} zip_io IO implementation that produces the template .zip
12
+ # @see {ContainedMr.new_template}
18
13
  def initialize(name_prefix, id, zip_io)
19
14
  @name_prefix = name_prefix
20
15
  @id = id
21
16
  @image_id = nil
22
- @definition = nil
23
17
  @item_count = nil
18
+ @_definition = nil
24
19
 
25
20
  tar_buffer = StringIO.new
26
21
  process_zip zip_io, tar_buffer
@@ -31,6 +26,8 @@ class ContainedMr::Template
31
26
  # Tears down the template's state.
32
27
  #
33
28
  # This removes the template's base Docker image.
29
+ #
30
+ # @return {ContainedMr::Template} self
34
31
  def destroy!
35
32
  unless @image_id.nil?
36
33
  # HACK(pwnall): Trick docker-api into issuing a DELETE request by tag.
@@ -38,63 +35,17 @@ class ContainedMr::Template
38
35
  image.remove
39
36
  @image_id = nil
40
37
  end
38
+ self
41
39
  end
42
40
 
43
- # Computes the Dockerfile used to build a job's mapper image.
44
- #
45
- # @return {String} the Dockerfile
46
- def mapper_dockerfile
47
- job_dockerfile @definition['mapper'] || {}, 'input'
48
- end
49
-
50
- # Computes the Dockerfile used to build a job's reducer image.
51
- #
52
- # @return {String} the Dockerfile
53
- def reducer_dockerfile
54
- job_dockerfile @definition['reducer'] || {}, '.'
55
- end
56
-
57
- # @return {String} tag applied to the template's base Docker image
58
- def image_tag
59
- "#{@name_prefix}/base.#{@id}"
60
- end
61
-
62
- # Computes the environment variables to be set in a mapper container.
41
+ # The class instantiated by {ContainedMr::TemplateLogic#new_job}.
63
42
  #
64
- # @param {Number} i the mapper number
65
- # @return {Array<String>} environment variables to be set in the mapper
66
- def mapper_env(i)
67
- [ "ITEM=#{i}", "ITEMS=#{@item_count.to_s}" ]
43
+ # @return {Class} by default {ContainedMr::Job}; tests might want to stub
44
+ # this method and have it return {ContainedMr::Mock::Job}
45
+ def job_class
46
+ ContainedMr::Job
68
47
  end
69
48
 
70
- # Computes the environment variables to be set in the reducer container.
71
- #
72
- # @return {Array<String>} environment variables to be set in the mapper
73
- def reducer_env
74
- [ "ITEMS=#{@item_count.to_s}" ]
75
- end
76
-
77
- # @return {String} the map output's path in the mapper Docker container
78
- def mapper_output_path
79
- (@definition['mapper'] || {})['output'] || '/output'
80
- end
81
-
82
- # @return {String} the reducer output's path in the reducer Docker container
83
- def reducer_output_path
84
- (@definition['reducer'] || {})['output'] || '/output'
85
- end
86
-
87
- # @private common code from mapper_dockerfile and reducer_dockerfile
88
- def job_dockerfile(job_definition, input_source)
89
- <<DOCKER_END
90
- FROM #{@image_id}
91
- COPY #{input_source} #{job_definition['input'] || '/input'}
92
- WORKDIR #{job_definition['chdir'] || '/'}
93
- ENTRYPOINT #{JSON.dump(job_definition['cmd'] || ['/bin/sh'])}
94
- DOCKER_END
95
- end
96
- private :job_dockerfile
97
-
98
49
  # Reads the template .zip and parses the definition.
99
50
  #
100
51
  # @param {IO} zip_io IO implementation that produces the .zip file
@@ -120,17 +71,7 @@ DOCKER_END
120
71
  end
121
72
  end
122
73
  end
123
-
124
- # Reads the template's definition, using data at the given path.
125
- #
126
- # @param {IO} yaml_io IO implementation that produces the .yaml file
127
- # containing the definition
128
- def read_definition(yaml_io)
129
- @definition = YAML.load yaml_io.read
130
-
131
- @item_count = @definition['items'] || 1
132
- end
133
- private :read_definition
74
+ private :process_zip
134
75
 
135
76
  # Builds the template's Docker image, using data at the given path.
136
77
  #
@@ -0,0 +1,91 @@
1
+ # Logic shared by {ContainedMr::Template} and {ContainedMr::Mock::Template}.
2
+ module ContainedMr::TemplateLogic
3
+ # @return {String} prepended to Docker objects, for identification purposes
4
+ attr_reader :name_prefix
5
+
6
+ # @return {String} the template's unique identifier
7
+ attr_reader :id
8
+
9
+ # @return {Number} the number of mapper jobs specified by this template
10
+ attr_reader :item_count
11
+
12
+ # @return {String} image_id the unique ID of the Docker image used as a base
13
+ # for images built by jobs derived from this template
14
+ attr_reader :image_id
15
+
16
+ # Creates a job using this template.
17
+ #
18
+ # @param {String} id the job's unique ID
19
+ # @param {Hash<String, Object>} json_options job options, extracted from JSON
20
+ # @return {ContainedMr::Job} a newly created job that uses this template
21
+ def new_job(id, json_options)
22
+ job_class.new self, id, json_options
23
+ end
24
+
25
+ # Computes the Dockerfile used to build a job's mapper image.
26
+ #
27
+ # @return {String} the Dockerfile
28
+ def mapper_dockerfile
29
+ job_dockerfile @_definition['mapper'] || {}, 'input'
30
+ end
31
+
32
+ # Computes the Dockerfile used to build a job's reducer image.
33
+ #
34
+ # @return {String} the Dockerfile
35
+ def reducer_dockerfile
36
+ job_dockerfile @_definition['reducer'] || {}, '.'
37
+ end
38
+
39
+ # @return {String} tag applied to the template's base Docker image
40
+ def image_tag
41
+ "#{@name_prefix}/base.#{@id}"
42
+ end
43
+
44
+ # Computes the environment variables to be set in a mapper container.
45
+ #
46
+ # @param {Number} i the mapper number
47
+ # @return {Array<String>} environment variables to be set in the mapper
48
+ def mapper_env(i)
49
+ [ "ITEM=#{i}", "ITEMS=#{@item_count.to_s}" ]
50
+ end
51
+
52
+ # Computes the environment variables to be set in the reducer container.
53
+ #
54
+ # @return {Array<String>} environment variables to be set in the mapper
55
+ def reducer_env
56
+ [ "ITEMS=#{@item_count.to_s}" ]
57
+ end
58
+
59
+ # @return {String} the map output's path in the mapper Docker container
60
+ def mapper_output_path
61
+ (@_definition['mapper'] || {})['output'] || '/output'
62
+ end
63
+
64
+ # @return {String} the reducer output's path in the reducer Docker container
65
+ def reducer_output_path
66
+ (@_definition['reducer'] || {})['output'] || '/output'
67
+ end
68
+
69
+ # @private common code from mapper_dockerfile and reducer_dockerfile
70
+ def job_dockerfile(job_definition, input_source)
71
+ <<DOCKER_END
72
+ FROM #{@image_id}
73
+ COPY #{input_source} #{job_definition['input'] || '/input'}
74
+ WORKDIR #{job_definition['chdir'] || '/'}
75
+ ENTRYPOINT #{JSON.dump(job_definition['cmd'] || ['/bin/sh'])}
76
+ DOCKER_END
77
+ end
78
+ private :job_dockerfile
79
+
80
+ # Reads the template's definition, using data at the given path.
81
+ #
82
+ # @param {IO} yaml_io IO implementation that produces the .yaml file
83
+ # containing the definition
84
+ def read_definition(yaml_io)
85
+ @_definition = YAML.load yaml_io.read
86
+ @_definition.freeze
87
+
88
+ @item_count = @_definition['items'] || 1
89
+ end
90
+ private :read_definition
91
+ end
data/lib/contained_mr.rb CHANGED
@@ -1,8 +1,15 @@
1
- # Namespace.
2
- module ContainedMr
3
- end
1
+ require_relative 'contained_mr/namespace.rb'
2
+
3
+ require_relative 'contained_mr/job_logic.rb'
4
+ require_relative 'contained_mr/runner_logic.rb'
5
+ require_relative 'contained_mr/template_logic.rb'
4
6
 
5
7
  require_relative 'contained_mr/cleaner.rb'
6
8
  require_relative 'contained_mr/job.rb'
7
9
  require_relative 'contained_mr/runner.rb'
8
10
  require_relative 'contained_mr/template.rb'
11
+
12
+ require_relative 'contained_mr/mock.rb'
13
+ require_relative 'contained_mr/mock/job.rb'
14
+ require_relative 'contained_mr/mock/runner.rb'
15
+ require_relative 'contained_mr/mock/template.rb'
@@ -0,0 +1,75 @@
1
+ module JobStateCases
2
+ def test_build_mapper_image_twice
3
+ @job.build_mapper_image File.read('testdata/input.hello')
4
+ begin
5
+ @job.build_mapper_image File.read('testdata/input.hello')
6
+ flunk 'No exception thrown'
7
+ rescue RuntimeError => e
8
+ assert_instance_of RuntimeError, e
9
+ assert_equal 'Mapper image already exists', e.message
10
+ end
11
+ end
12
+
13
+ def test_run_mapper_without_image
14
+ begin
15
+ @job.run_mapper 1
16
+ flunk 'No exception thrown'
17
+ rescue RuntimeError => e
18
+ assert_instance_of RuntimeError, e
19
+ assert_equal 'Mapper image does not exist', e.message
20
+ end
21
+ end
22
+
23
+ def test_run_invalid_mapper
24
+ begin
25
+ @job.run_mapper 4
26
+ flunk 'No exception thrown'
27
+ rescue ArgumentError => e
28
+ assert_instance_of ArgumentError, e
29
+ assert_equal 'Invalid mapper number 4', e.message
30
+ end
31
+ end
32
+
33
+ def test_invalid_mapper_runner
34
+ begin
35
+ @job.mapper_runner 4
36
+ flunk 'No exception thrown'
37
+ rescue ArgumentError => e
38
+ assert_instance_of ArgumentError, e
39
+ assert_equal 'Invalid mapper number 4', e.message
40
+ end
41
+ end
42
+
43
+ def test_build_reducer_image_without_mapper_results
44
+ begin
45
+ @job.build_reducer_image
46
+ flunk 'No exception thrown'
47
+ rescue RuntimeError => e
48
+ assert_instance_of RuntimeError, e
49
+ assert_equal 'Not all mappers ran', e.message
50
+ end
51
+ end
52
+
53
+ def test_build_reducer_image_twice
54
+ @job.build_mapper_image File.read('testdata/input.hello')
55
+ 1.upto(3) { |i| @job.run_mapper i }
56
+ @job.build_reducer_image
57
+ begin
58
+ @job.build_reducer_image
59
+ flunk 'No exception thrown'
60
+ rescue RuntimeError => e
61
+ assert_instance_of RuntimeError, e
62
+ assert_equal 'Reducer image already exists', e.message
63
+ end
64
+ end
65
+
66
+ def test_run_reducer_without_image
67
+ begin
68
+ @job.run_reducer
69
+ flunk 'No exception thrown'
70
+ rescue RuntimeError => e
71
+ assert_instance_of RuntimeError, e
72
+ assert_equal 'Reducer image does not exist', e.message
73
+ end
74
+ end
75
+ end
data/test/helper.rb CHANGED
@@ -24,6 +24,7 @@ rescue Bundler::BundlerError => e
24
24
  exit e.status_code
25
25
  end
26
26
  require 'minitest/autorun'
27
+ require 'mocha/mini_test'
27
28
 
28
29
  $LOAD_PATH.unshift(File.dirname(__FILE__))
29
30
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
data/test/test_cleaner.rb CHANGED
@@ -2,10 +2,10 @@ require 'helper'
2
2
 
3
3
  class TestCleaner < MiniTest::Test
4
4
  def setup
5
- @template = ContainedMr::Template.new 'contained_mrtests', 'hello',
5
+ @template = ContainedMr.new_template 'contained_mrtests', 'hello',
6
6
  StringIO.new(File.binread('testdata/hello.zip'))
7
- @job = ContainedMr::Job.new @template, 'testjob',
8
- JSON.load(File.read('testdata/job.hello'))
7
+ @job = @template.new_job 'testjob',
8
+ JSON.load(File.read('testdata/job.hello'))
9
9
  @job.build_mapper_image File.read('testdata/input.hello')
10
10
 
11
11
  @cleaner = ContainedMr::Cleaner.new 'contained_mrtests'
@@ -22,10 +22,10 @@ class TestCleaner < MiniTest::Test
22
22
  end
23
23
 
24
24
  def test_destroy_all_with_duplicates
25
- template2 = ContainedMr::Template.new 'contained_mrtests', 'hello2',
25
+ template2 = ContainedMr.new_template 'contained_mrtests', 'hello2',
26
26
  StringIO.new(File.binread('testdata/hello.zip'))
27
- job2 = ContainedMr::Job.new template2, 'testjob2',
28
- JSON.load(File.read('testdata/job.hello'))
27
+ job2 = template2.new_job 'testjob2',
28
+ JSON.load(File.read('testdata/job.hello'))
29
29
  job2.build_mapper_image File.read('testdata/input.hello')
30
30
  @cleaner.destroy_all!
31
31
  assert_raises Docker::Error::NotFoundError do
@@ -0,0 +1,15 @@
1
+ require 'helper'
2
+
3
+ class TestContainedMr < MiniTest::Test
4
+ def test_template_class
5
+ assert_equal ContainedMr::Template, ContainedMr.template_class
6
+ end
7
+
8
+ def test_new_template
9
+ ContainedMr.stubs(:template_class).returns ContainedMr::Mock::Template
10
+
11
+ template = ContainedMr.new_template 'contained_mrtests', 'hello',
12
+ StringIO.new(File.binread('testdata/hello.zip'))
13
+ assert_instance_of ContainedMr::Mock::Template, template
14
+ end
15
+ end