|
@@ -1,509 +1,656 @@
|
|
|
-SandboxedModule = require('sandboxed-module')
|
|
|
|
|
-sinon = require('sinon')
|
|
|
|
|
-require('chai').should()
|
|
|
|
|
-expect = require('chai').expect
|
|
|
|
|
-require "coffee-script"
|
|
|
|
|
-modulePath = require('path').join __dirname, '../../../app/coffee/DockerRunner'
|
|
|
|
|
-Path = require "path"
|
|
|
|
|
-
|
|
|
|
|
-describe "DockerRunner", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @container = container = {}
|
|
|
|
|
- @DockerRunner = SandboxedModule.require modulePath, requires:
|
|
|
|
|
- "settings-sharelatex": @Settings =
|
|
|
|
|
- clsi: docker: {}
|
|
|
|
|
|
|
+/*
|
|
|
|
|
+ * decaffeinate suggestions:
|
|
|
|
|
+ * DS101: Remove unnecessary use of Array.from
|
|
|
|
|
+ * DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
|
+ * DS206: Consider reworking classes to avoid initClass
|
|
|
|
|
+ * DS207: Consider shorter variations of null checks
|
|
|
|
|
+ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
|
+ */
|
|
|
|
|
+const SandboxedModule = require('sandboxed-module');
|
|
|
|
|
+const sinon = require('sinon');
|
|
|
|
|
+require('chai').should();
|
|
|
|
|
+const { expect } = require('chai');
|
|
|
|
|
+require("coffee-script");
|
|
|
|
|
+const modulePath = require('path').join(__dirname, '../../../app/coffee/DockerRunner');
|
|
|
|
|
+const Path = require("path");
|
|
|
|
|
+
|
|
|
|
|
+describe("DockerRunner", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ let container, Docker, Timer;
|
|
|
|
|
+ this.container = (container = {});
|
|
|
|
|
+ this.DockerRunner = SandboxedModule.require(modulePath, { requires: {
|
|
|
|
|
+ "settings-sharelatex": (this.Settings = {
|
|
|
|
|
+ clsi: { docker: {}
|
|
|
|
|
+ },
|
|
|
path: {}
|
|
path: {}
|
|
|
- "logger-sharelatex": @logger = {
|
|
|
|
|
|
|
+ }),
|
|
|
|
|
+ "logger-sharelatex": (this.logger = {
|
|
|
log: sinon.stub(),
|
|
log: sinon.stub(),
|
|
|
error: sinon.stub(),
|
|
error: sinon.stub(),
|
|
|
info: sinon.stub(),
|
|
info: sinon.stub(),
|
|
|
warn: sinon.stub()
|
|
warn: sinon.stub()
|
|
|
|
|
+ }),
|
|
|
|
|
+ "dockerode": (Docker = (function() {
|
|
|
|
|
+ Docker = class Docker {
|
|
|
|
|
+ static initClass() {
|
|
|
|
|
+ this.prototype.getContainer = sinon.stub().returns(container);
|
|
|
|
|
+ this.prototype.createContainer = sinon.stub().yields(null, container);
|
|
|
|
|
+ this.prototype.listContainers = sinon.stub();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ Docker.initClass();
|
|
|
|
|
+ return Docker;
|
|
|
|
|
+ })()),
|
|
|
|
|
+ "fs": (this.fs = { stat: sinon.stub().yields(null,{isDirectory(){ return true; }}) }),
|
|
|
|
|
+ "./Metrics": {
|
|
|
|
|
+ Timer: (Timer = class Timer {
|
|
|
|
|
+ done() {}
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ "./LockManager": {
|
|
|
|
|
+ runWithLock(key, runner, callback) { return runner(callback); }
|
|
|
}
|
|
}
|
|
|
- "dockerode": class Docker
|
|
|
|
|
- getContainer: sinon.stub().returns(container)
|
|
|
|
|
- createContainer: sinon.stub().yields(null, container)
|
|
|
|
|
- listContainers: sinon.stub()
|
|
|
|
|
- "fs": @fs = { stat: sinon.stub().yields(null,{isDirectory:()->true}) }
|
|
|
|
|
- "./Metrics":
|
|
|
|
|
- Timer: class Timer
|
|
|
|
|
- done: () ->
|
|
|
|
|
- "./LockManager":
|
|
|
|
|
- runWithLock: (key, runner, callback) -> runner(callback)
|
|
|
|
|
- @Docker = Docker
|
|
|
|
|
- @getContainer = Docker::getContainer
|
|
|
|
|
- @createContainer = Docker::createContainer
|
|
|
|
|
- @listContainers = Docker::listContainers
|
|
|
|
|
-
|
|
|
|
|
- @directory = "/local/compile/directory"
|
|
|
|
|
- @mainFile = "main-file.tex"
|
|
|
|
|
- @compiler = "pdflatex"
|
|
|
|
|
- @image = "example.com/sharelatex/image:2016.2"
|
|
|
|
|
- @env = {}
|
|
|
|
|
- @callback = sinon.stub()
|
|
|
|
|
- @project_id = "project-id-123"
|
|
|
|
|
- @volumes =
|
|
|
|
|
- "/local/compile/directory": "/compile"
|
|
|
|
|
- @Settings.clsi.docker.image = @defaultImage = "default-image"
|
|
|
|
|
- @Settings.clsi.docker.env = PATH: "mock-path"
|
|
|
|
|
-
|
|
|
|
|
- describe "run", ->
|
|
|
|
|
- beforeEach (done)->
|
|
|
|
|
- @DockerRunner._getContainerOptions = sinon.stub().returns(@options = {mockoptions: "foo"})
|
|
|
|
|
- @DockerRunner._fingerprintContainer = sinon.stub().returns(@fingerprint = "fingerprint")
|
|
|
|
|
-
|
|
|
|
|
- @name = "project-#{@project_id}-#{@fingerprint}"
|
|
|
|
|
-
|
|
|
|
|
- @command = ["mock", "command", "--outdir=$COMPILE_DIR"]
|
|
|
|
|
- @command_with_dir = ["mock", "command", "--outdir=/compile"]
|
|
|
|
|
- @timeout = 42000
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- describe "successfully", ->
|
|
|
|
|
- beforeEach (done)->
|
|
|
|
|
- @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output")
|
|
|
|
|
- @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, (err, output)=>
|
|
|
|
|
- @callback(err, output)
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- it "should generate the options for the container", ->
|
|
|
|
|
- @DockerRunner._getContainerOptions
|
|
|
|
|
- .calledWith(@command_with_dir, @image, @volumes, @timeout)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should generate the fingerprint from the returned options", ->
|
|
|
|
|
- @DockerRunner._fingerprintContainer
|
|
|
|
|
- .calledWith(@options)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should do the run", ->
|
|
|
|
|
- @DockerRunner._runAndWaitForContainer
|
|
|
|
|
- .calledWith(@options, @volumes, @timeout)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback", ->
|
|
|
|
|
- @callback.calledWith(null, @output).should.equal true
|
|
|
|
|
-
|
|
|
|
|
- describe 'when path.sandboxedCompilesHostDir is set', ->
|
|
|
|
|
-
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles'
|
|
|
|
|
- @directory = '/var/lib/sharelatex/data/compiles/xyz'
|
|
|
|
|
- @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output")
|
|
|
|
|
- @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback
|
|
|
|
|
-
|
|
|
|
|
- it 'should re-write the bind directory', ->
|
|
|
|
|
- volumes = @DockerRunner._runAndWaitForContainer.lastCall.args[1]
|
|
|
|
|
- expect(volumes).to.deep.equal {
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ );
|
|
|
|
|
+ this.Docker = Docker;
|
|
|
|
|
+ this.getContainer = Docker.prototype.getContainer;
|
|
|
|
|
+ this.createContainer = Docker.prototype.createContainer;
|
|
|
|
|
+ this.listContainers = Docker.prototype.listContainers;
|
|
|
|
|
+
|
|
|
|
|
+ this.directory = "/local/compile/directory";
|
|
|
|
|
+ this.mainFile = "main-file.tex";
|
|
|
|
|
+ this.compiler = "pdflatex";
|
|
|
|
|
+ this.image = "example.com/sharelatex/image:2016.2";
|
|
|
|
|
+ this.env = {};
|
|
|
|
|
+ this.callback = sinon.stub();
|
|
|
|
|
+ this.project_id = "project-id-123";
|
|
|
|
|
+ this.volumes =
|
|
|
|
|
+ {"/local/compile/directory": "/compile"};
|
|
|
|
|
+ this.Settings.clsi.docker.image = (this.defaultImage = "default-image");
|
|
|
|
|
+ return this.Settings.clsi.docker.env = {PATH: "mock-path"};
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("run", function() {
|
|
|
|
|
+ beforeEach(function(done){
|
|
|
|
|
+ this.DockerRunner._getContainerOptions = sinon.stub().returns(this.options = {mockoptions: "foo"});
|
|
|
|
|
+ this.DockerRunner._fingerprintContainer = sinon.stub().returns(this.fingerprint = "fingerprint");
|
|
|
|
|
+
|
|
|
|
|
+ this.name = `project-${this.project_id}-${this.fingerprint}`;
|
|
|
|
|
+
|
|
|
|
|
+ this.command = ["mock", "command", "--outdir=$COMPILE_DIR"];
|
|
|
|
|
+ this.command_with_dir = ["mock", "command", "--outdir=/compile"];
|
|
|
|
|
+ this.timeout = 42000;
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("successfully", function() {
|
|
|
|
|
+ beforeEach(function(done){
|
|
|
|
|
+ this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output"));
|
|
|
|
|
+ return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, (err, output)=> {
|
|
|
|
|
+ this.callback(err, output);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should generate the options for the container", function() {
|
|
|
|
|
+ return this.DockerRunner._getContainerOptions
|
|
|
|
|
+ .calledWith(this.command_with_dir, this.image, this.volumes, this.timeout)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should generate the fingerprint from the returned options", function() {
|
|
|
|
|
+ return this.DockerRunner._fingerprintContainer
|
|
|
|
|
+ .calledWith(this.options)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should do the run", function() {
|
|
|
|
|
+ return this.DockerRunner._runAndWaitForContainer
|
|
|
|
|
+ .calledWith(this.options, this.volumes, this.timeout)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should call the callback", function() {
|
|
|
|
|
+ return this.callback.calledWith(null, this.output).should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe('when path.sandboxedCompilesHostDir is set', function() {
|
|
|
|
|
+
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.Settings.path.sandboxedCompilesHostDir = '/some/host/dir/compiles';
|
|
|
|
|
+ this.directory = '/var/lib/sharelatex/data/compiles/xyz';
|
|
|
|
|
+ this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output"));
|
|
|
|
|
+ return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('should re-write the bind directory', function() {
|
|
|
|
|
+ const volumes = this.DockerRunner._runAndWaitForContainer.lastCall.args[1];
|
|
|
|
|
+ return expect(volumes).to.deep.equal({
|
|
|
'/some/host/dir/compiles/xyz': '/compile'
|
|
'/some/host/dir/compiles/xyz': '/compile'
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback", ->
|
|
|
|
|
- @callback.calledWith(null, @output).should.equal true
|
|
|
|
|
-
|
|
|
|
|
- describe "when the run throws an error", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- firstTime = true
|
|
|
|
|
- @output = "mock-output"
|
|
|
|
|
- @DockerRunner._runAndWaitForContainer = (options, volumes, timeout, callback = (error, output)->) =>
|
|
|
|
|
- if firstTime
|
|
|
|
|
- firstTime = false
|
|
|
|
|
- callback new Error("HTTP code is 500 which indicates error: server error")
|
|
|
|
|
- else
|
|
|
|
|
- callback(null, @output)
|
|
|
|
|
- sinon.spy @DockerRunner, "_runAndWaitForContainer"
|
|
|
|
|
- @DockerRunner.destroyContainer = sinon.stub().callsArg(3)
|
|
|
|
|
- @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback
|
|
|
|
|
-
|
|
|
|
|
- it "should do the run twice", ->
|
|
|
|
|
- @DockerRunner._runAndWaitForContainer
|
|
|
|
|
- .calledTwice.should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should destroy the container in between", ->
|
|
|
|
|
- @DockerRunner.destroyContainer
|
|
|
|
|
- .calledWith(@name, null)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback", ->
|
|
|
|
|
- @callback.calledWith(null, @output).should.equal true
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should call the callback", function() {
|
|
|
|
|
+ return this.callback.calledWith(null, this.output).should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("when the run throws an error", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ let firstTime = true;
|
|
|
|
|
+ this.output = "mock-output";
|
|
|
|
|
+ this.DockerRunner._runAndWaitForContainer = (options, volumes, timeout, callback) => {
|
|
|
|
|
+ if (callback == null) { callback = function(error, output){}; }
|
|
|
|
|
+ if (firstTime) {
|
|
|
|
|
+ firstTime = false;
|
|
|
|
|
+ return callback(new Error("HTTP code is 500 which indicates error: server error"));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return callback(null, this.output);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ sinon.spy(this.DockerRunner, "_runAndWaitForContainer");
|
|
|
|
|
+ this.DockerRunner.destroyContainer = sinon.stub().callsArg(3);
|
|
|
|
|
+ return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should do the run twice", function() {
|
|
|
|
|
+ return this.DockerRunner._runAndWaitForContainer
|
|
|
|
|
+ .calledTwice.should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should destroy the container in between", function() {
|
|
|
|
|
+ return this.DockerRunner.destroyContainer
|
|
|
|
|
+ .calledWith(this.name, null)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should call the callback", function() {
|
|
|
|
|
+ return this.callback.calledWith(null, this.output).should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- describe "with no image", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output")
|
|
|
|
|
- @DockerRunner.run @project_id, @command, @directory, null, @timeout, @env, @callback
|
|
|
|
|
-
|
|
|
|
|
- it "should use the default image", ->
|
|
|
|
|
- @DockerRunner._getContainerOptions
|
|
|
|
|
- .calledWith(@command_with_dir, @defaultImage, @volumes, @timeout)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
|
|
+ describe("with no image", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output"));
|
|
|
|
|
+ return this.DockerRunner.run(this.project_id, this.command, this.directory, null, this.timeout, this.env, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should use the default image", function() {
|
|
|
|
|
+ return this.DockerRunner._getContainerOptions
|
|
|
|
|
+ .calledWith(this.command_with_dir, this.defaultImage, this.volumes, this.timeout)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- describe "with image override", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @Settings.texliveImageNameOveride = "overrideimage.com/something"
|
|
|
|
|
- @DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, @output = "mock-output")
|
|
|
|
|
- @DockerRunner.run @project_id, @command, @directory, @image, @timeout, @env, @callback
|
|
|
|
|
-
|
|
|
|
|
- it "should use the override and keep the tag", ->
|
|
|
|
|
- image = @DockerRunner._getContainerOptions.args[0][1]
|
|
|
|
|
- image.should.equal "overrideimage.com/something/image:2016.2"
|
|
|
|
|
-
|
|
|
|
|
- describe "_runAndWaitForContainer", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @options = {mockoptions: "foo", name: @name = "mock-name"}
|
|
|
|
|
- @DockerRunner.startContainer = (options, volumes, attachStreamHandler, callback) =>
|
|
|
|
|
- attachStreamHandler(null, @output = "mock-output")
|
|
|
|
|
- callback(null, @containerId = "container-id")
|
|
|
|
|
- sinon.spy @DockerRunner, "startContainer"
|
|
|
|
|
- @DockerRunner.waitForContainer = sinon.stub().callsArgWith(2, null, @exitCode = 42)
|
|
|
|
|
- @DockerRunner._runAndWaitForContainer @options, @volumes, @timeout, @callback
|
|
|
|
|
-
|
|
|
|
|
- it "should create/start the container", ->
|
|
|
|
|
- @DockerRunner.startContainer
|
|
|
|
|
- .calledWith(@options, @volumes)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should wait for the container to finish", ->
|
|
|
|
|
- @DockerRunner.waitForContainer
|
|
|
|
|
- .calledWith(@name, @timeout)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback with the output", ->
|
|
|
|
|
- @callback.calledWith(null, @output).should.equal true
|
|
|
|
|
-
|
|
|
|
|
- describe "startContainer", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @attachStreamHandler = sinon.stub()
|
|
|
|
|
- @attachStreamHandler.cock = true
|
|
|
|
|
- @options = {mockoptions: "foo", name: "mock-name"}
|
|
|
|
|
- @container.inspect = sinon.stub().callsArgWith(0)
|
|
|
|
|
- @DockerRunner.attachToContainer = (containerId, attachStreamHandler, cb)=>
|
|
|
|
|
- attachStreamHandler()
|
|
|
|
|
- cb()
|
|
|
|
|
- sinon.spy @DockerRunner, "attachToContainer"
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- describe "when the container exists", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @container.inspect = sinon.stub().callsArgWith(0)
|
|
|
|
|
- @container.start = sinon.stub().yields()
|
|
|
|
|
-
|
|
|
|
|
- @DockerRunner.startContainer @options, @volumes, @callback, ->
|
|
|
|
|
-
|
|
|
|
|
- it "should start the container with the given name", ->
|
|
|
|
|
- @getContainer
|
|
|
|
|
- .calledWith(@options.name)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
- @container.start
|
|
|
|
|
|
|
+ return describe("with image override", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.Settings.texliveImageNameOveride = "overrideimage.com/something";
|
|
|
|
|
+ this.DockerRunner._runAndWaitForContainer = sinon.stub().callsArgWith(3, null, (this.output = "mock-output"));
|
|
|
|
|
+ return this.DockerRunner.run(this.project_id, this.command, this.directory, this.image, this.timeout, this.env, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should use the override and keep the tag", function() {
|
|
|
|
|
+ const image = this.DockerRunner._getContainerOptions.args[0][1];
|
|
|
|
|
+ return image.should.equal("overrideimage.com/something/image:2016.2");
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("_runAndWaitForContainer", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.options = {mockoptions: "foo", name: (this.name = "mock-name")};
|
|
|
|
|
+ this.DockerRunner.startContainer = (options, volumes, attachStreamHandler, callback) => {
|
|
|
|
|
+ attachStreamHandler(null, (this.output = "mock-output"));
|
|
|
|
|
+ return callback(null, (this.containerId = "container-id"));
|
|
|
|
|
+ };
|
|
|
|
|
+ sinon.spy(this.DockerRunner, "startContainer");
|
|
|
|
|
+ this.DockerRunner.waitForContainer = sinon.stub().callsArgWith(2, null, (this.exitCode = 42));
|
|
|
|
|
+ return this.DockerRunner._runAndWaitForContainer(this.options, this.volumes, this.timeout, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should create/start the container", function() {
|
|
|
|
|
+ return this.DockerRunner.startContainer
|
|
|
|
|
+ .calledWith(this.options, this.volumes)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should wait for the container to finish", function() {
|
|
|
|
|
+ return this.DockerRunner.waitForContainer
|
|
|
|
|
+ .calledWith(this.name, this.timeout)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should call the callback with the output", function() {
|
|
|
|
|
+ return this.callback.calledWith(null, this.output).should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("startContainer", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.attachStreamHandler = sinon.stub();
|
|
|
|
|
+ this.attachStreamHandler.cock = true;
|
|
|
|
|
+ this.options = {mockoptions: "foo", name: "mock-name"};
|
|
|
|
|
+ this.container.inspect = sinon.stub().callsArgWith(0);
|
|
|
|
|
+ this.DockerRunner.attachToContainer = (containerId, attachStreamHandler, cb)=> {
|
|
|
|
|
+ attachStreamHandler();
|
|
|
|
|
+ return cb();
|
|
|
|
|
+ };
|
|
|
|
|
+ return sinon.spy(this.DockerRunner, "attachToContainer");
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ describe("when the container exists", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.container.inspect = sinon.stub().callsArgWith(0);
|
|
|
|
|
+ this.container.start = sinon.stub().yields();
|
|
|
|
|
+
|
|
|
|
|
+ return this.DockerRunner.startContainer(this.options, this.volumes, this.callback, function() {});
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should start the container with the given name", function() {
|
|
|
|
|
+ this.getContainer
|
|
|
|
|
+ .calledWith(this.options.name)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ return this.container.start
|
|
|
.called
|
|
.called
|
|
|
- .should.equal true
|
|
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- it "should not try to create the container", ->
|
|
|
|
|
- @createContainer.called.should.equal false
|
|
|
|
|
|
|
+ it("should not try to create the container", function() {
|
|
|
|
|
+ return this.createContainer.called.should.equal(false);
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- it "should attach to the container", ->
|
|
|
|
|
- @DockerRunner.attachToContainer.called.should.equal true
|
|
|
|
|
|
|
+ it("should attach to the container", function() {
|
|
|
|
|
+ return this.DockerRunner.attachToContainer.called.should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- it "should call the callback", ->
|
|
|
|
|
- @callback.called.should.equal true
|
|
|
|
|
|
|
+ it("should call the callback", function() {
|
|
|
|
|
+ return this.callback.called.should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- it "should attach before the container starts", ->
|
|
|
|
|
- sinon.assert.callOrder(@DockerRunner.attachToContainer, @container.start)
|
|
|
|
|
|
|
+ return it("should attach before the container starts", function() {
|
|
|
|
|
+ return sinon.assert.callOrder(this.DockerRunner.attachToContainer, this.container.start);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- describe "when the container does not exist", ->
|
|
|
|
|
- beforeEach ()->
|
|
|
|
|
- exists = false
|
|
|
|
|
- @container.start = sinon.stub().yields()
|
|
|
|
|
- @container.inspect = sinon.stub().callsArgWith(0, {statusCode:404})
|
|
|
|
|
- @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback
|
|
|
|
|
-
|
|
|
|
|
- it "should create the container", ->
|
|
|
|
|
- @createContainer
|
|
|
|
|
- .calledWith(@options)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback and stream handler", ->
|
|
|
|
|
- @attachStreamHandler.called.should.equal true
|
|
|
|
|
- @callback.called.should.equal true
|
|
|
|
|
|
|
+ describe("when the container does not exist", function() {
|
|
|
|
|
+ beforeEach(function(){
|
|
|
|
|
+ const exists = false;
|
|
|
|
|
+ this.container.start = sinon.stub().yields();
|
|
|
|
|
+ this.container.inspect = sinon.stub().callsArgWith(0, {statusCode:404});
|
|
|
|
|
+ return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should create the container", function() {
|
|
|
|
|
+ return this.createContainer
|
|
|
|
|
+ .calledWith(this.options)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should call the callback and stream handler", function() {
|
|
|
|
|
+ this.attachStreamHandler.called.should.equal(true);
|
|
|
|
|
+ return this.callback.called.should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- it "should attach to the container", ->
|
|
|
|
|
- @DockerRunner.attachToContainer.called.should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should attach before the container starts", ->
|
|
|
|
|
- sinon.assert.callOrder(@DockerRunner.attachToContainer, @container.start)
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- describe "when the container is already running", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- error = new Error("HTTP code is 304 which indicates error: server error - start: Cannot start container #{@name}: The container MOCKID is already running.")
|
|
|
|
|
- error.statusCode = 304
|
|
|
|
|
- @container.start = sinon.stub().yields(error)
|
|
|
|
|
- @container.inspect = sinon.stub().callsArgWith(0)
|
|
|
|
|
- @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback
|
|
|
|
|
-
|
|
|
|
|
- it "should not try to create the container", ->
|
|
|
|
|
- @createContainer.called.should.equal false
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback and stream handler without an error", ->
|
|
|
|
|
- @attachStreamHandler.called.should.equal true
|
|
|
|
|
- @callback.called.should.equal true
|
|
|
|
|
-
|
|
|
|
|
- describe "when a volume does not exist", ->
|
|
|
|
|
- beforeEach ()->
|
|
|
|
|
- @fs.stat = sinon.stub().yields(new Error("no such path"))
|
|
|
|
|
- @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback
|
|
|
|
|
-
|
|
|
|
|
- it "should not try to create the container", ->
|
|
|
|
|
- @createContainer.called.should.equal false
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback with an error", ->
|
|
|
|
|
- @callback.calledWith(new Error()).should.equal true
|
|
|
|
|
-
|
|
|
|
|
- describe "when a volume exists but is not a directory", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @fs.stat = sinon.stub().yields(null, {isDirectory: () -> return false})
|
|
|
|
|
- @DockerRunner.startContainer @options, @volumes, @attachStreamHandler, @callback
|
|
|
|
|
-
|
|
|
|
|
- it "should not try to create the container", ->
|
|
|
|
|
- @createContainer.called.should.equal false
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback with an error", ->
|
|
|
|
|
- @callback.calledWith(new Error()).should.equal true
|
|
|
|
|
-
|
|
|
|
|
- describe "when a volume does not exist, but sibling-containers are used", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @fs.stat = sinon.stub().yields(new Error("no such path"))
|
|
|
|
|
- @Settings.path.sandboxedCompilesHostDir = '/some/path'
|
|
|
|
|
- @container.start = sinon.stub().yields()
|
|
|
|
|
- @DockerRunner.startContainer @options, @volumes, @callback
|
|
|
|
|
-
|
|
|
|
|
- afterEach ->
|
|
|
|
|
- delete @Settings.path.sandboxedCompilesHostDir
|
|
|
|
|
-
|
|
|
|
|
- it "should start the container with the given name", ->
|
|
|
|
|
- @getContainer
|
|
|
|
|
- .calledWith(@options.name)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
- @container.start
|
|
|
|
|
|
|
+ it("should attach to the container", function() {
|
|
|
|
|
+ return this.DockerRunner.attachToContainer.called.should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should attach before the container starts", function() {
|
|
|
|
|
+ return sinon.assert.callOrder(this.DockerRunner.attachToContainer, this.container.start);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ describe("when the container is already running", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ const error = new Error(`HTTP code is 304 which indicates error: server error - start: Cannot start container ${this.name}: The container MOCKID is already running.`);
|
|
|
|
|
+ error.statusCode = 304;
|
|
|
|
|
+ this.container.start = sinon.stub().yields(error);
|
|
|
|
|
+ this.container.inspect = sinon.stub().callsArgWith(0);
|
|
|
|
|
+ return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should not try to create the container", function() {
|
|
|
|
|
+ return this.createContainer.called.should.equal(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should call the callback and stream handler without an error", function() {
|
|
|
|
|
+ this.attachStreamHandler.called.should.equal(true);
|
|
|
|
|
+ return this.callback.called.should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("when a volume does not exist", function() {
|
|
|
|
|
+ beforeEach(function(){
|
|
|
|
|
+ this.fs.stat = sinon.stub().yields(new Error("no such path"));
|
|
|
|
|
+ return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should not try to create the container", function() {
|
|
|
|
|
+ return this.createContainer.called.should.equal(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should call the callback with an error", function() {
|
|
|
|
|
+ return this.callback.calledWith(new Error()).should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("when a volume exists but is not a directory", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.fs.stat = sinon.stub().yields(null, {isDirectory() { return false; }});
|
|
|
|
|
+ return this.DockerRunner.startContainer(this.options, this.volumes, this.attachStreamHandler, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should not try to create the container", function() {
|
|
|
|
|
+ return this.createContainer.called.should.equal(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should call the callback with an error", function() {
|
|
|
|
|
+ return this.callback.calledWith(new Error()).should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("when a volume does not exist, but sibling-containers are used", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.fs.stat = sinon.stub().yields(new Error("no such path"));
|
|
|
|
|
+ this.Settings.path.sandboxedCompilesHostDir = '/some/path';
|
|
|
|
|
+ this.container.start = sinon.stub().yields();
|
|
|
|
|
+ return this.DockerRunner.startContainer(this.options, this.volumes, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ afterEach(function() {
|
|
|
|
|
+ return delete this.Settings.path.sandboxedCompilesHostDir;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should start the container with the given name", function() {
|
|
|
|
|
+ this.getContainer
|
|
|
|
|
+ .calledWith(this.options.name)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ return this.container.start
|
|
|
.called
|
|
.called
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should not try to create the container", ->
|
|
|
|
|
- @createContainer.called.should.equal false
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback", ->
|
|
|
|
|
- @callback.called.should.equal true
|
|
|
|
|
- @callback.calledWith(new Error()).should.equal false
|
|
|
|
|
-
|
|
|
|
|
- describe "when the container tries to be created, but already has been (race condition)", ->
|
|
|
|
|
-
|
|
|
|
|
- describe "waitForContainer", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @containerId = "container-id"
|
|
|
|
|
- @timeout = 5000
|
|
|
|
|
- @container.wait = sinon.stub().yields(null, StatusCode: @statusCode = 42)
|
|
|
|
|
- @container.kill = sinon.stub().yields()
|
|
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should not try to create the container", function() {
|
|
|
|
|
+ return this.createContainer.called.should.equal(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should call the callback", function() {
|
|
|
|
|
+ this.callback.called.should.equal(true);
|
|
|
|
|
+ return this.callback.calledWith(new Error()).should.equal(false);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return describe("when the container tries to be created, but already has been (race condition)", function() {});
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("waitForContainer", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.containerId = "container-id";
|
|
|
|
|
+ this.timeout = 5000;
|
|
|
|
|
+ this.container.wait = sinon.stub().yields(null, {StatusCode: (this.statusCode = 42)});
|
|
|
|
|
+ return this.container.kill = sinon.stub().yields();
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- describe "when the container returns in time", ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @DockerRunner.waitForContainer @containerId, @timeout, @callback
|
|
|
|
|
-
|
|
|
|
|
- it "should wait for the container", ->
|
|
|
|
|
- @getContainer
|
|
|
|
|
- .calledWith(@containerId)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
- @container.wait
|
|
|
|
|
|
|
+ describe("when the container returns in time", function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ return this.DockerRunner.waitForContainer(this.containerId, this.timeout, this.callback);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should wait for the container", function() {
|
|
|
|
|
+ this.getContainer
|
|
|
|
|
+ .calledWith(this.containerId)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ return this.container.wait
|
|
|
.called
|
|
.called
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should call the callback with the exit", ->
|
|
|
|
|
- @callback
|
|
|
|
|
- .calledWith(null, @statusCode)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- describe "when the container does not return before the timeout", ->
|
|
|
|
|
- beforeEach (done) ->
|
|
|
|
|
- @container.wait = (callback = (error, exitCode) ->) ->
|
|
|
|
|
- setTimeout () ->
|
|
|
|
|
- callback(null, StatusCode: 42)
|
|
|
|
|
- , 100
|
|
|
|
|
- @timeout = 5
|
|
|
|
|
- @DockerRunner.waitForContainer @containerId, @timeout, (args...) =>
|
|
|
|
|
- @callback(args...)
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- it "should call kill on the container", ->
|
|
|
|
|
- @getContainer
|
|
|
|
|
- .calledWith(@containerId)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
- @container.kill
|
|
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should call the callback with the exit", function() {
|
|
|
|
|
+ return this.callback
|
|
|
|
|
+ .calledWith(null, this.statusCode)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return describe("when the container does not return before the timeout", function() {
|
|
|
|
|
+ beforeEach(function(done) {
|
|
|
|
|
+ this.container.wait = function(callback) {
|
|
|
|
|
+ if (callback == null) { callback = function(error, exitCode) {}; }
|
|
|
|
|
+ return setTimeout(() => callback(null, {StatusCode: 42})
|
|
|
|
|
+ , 100);
|
|
|
|
|
+ };
|
|
|
|
|
+ this.timeout = 5;
|
|
|
|
|
+ return this.DockerRunner.waitForContainer(this.containerId, this.timeout, (...args) => {
|
|
|
|
|
+ this.callback(...Array.from(args || []));
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should call kill on the container", function() {
|
|
|
|
|
+ this.getContainer
|
|
|
|
|
+ .calledWith(this.containerId)
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ return this.container.kill
|
|
|
.called
|
|
.called
|
|
|
- .should.equal true
|
|
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- it "should call the callback with an error", ->
|
|
|
|
|
- error = new Error("container timed out")
|
|
|
|
|
- error.timedout = true
|
|
|
|
|
- @callback
|
|
|
|
|
|
|
+ return it("should call the callback with an error", function() {
|
|
|
|
|
+ const error = new Error("container timed out");
|
|
|
|
|
+ error.timedout = true;
|
|
|
|
|
+ return this.callback
|
|
|
.calledWith(error)
|
|
.calledWith(error)
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- describe "destroyOldContainers", ->
|
|
|
|
|
- beforeEach (done) ->
|
|
|
|
|
- oneHourInSeconds = 60 * 60
|
|
|
|
|
- oneHourInMilliseconds = oneHourInSeconds * 1000
|
|
|
|
|
- nowInSeconds = Date.now()/1000
|
|
|
|
|
- @containers = [{
|
|
|
|
|
- Name: "/project-old-container-name"
|
|
|
|
|
- Id: "old-container-id"
|
|
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe("destroyOldContainers", function() {
|
|
|
|
|
+ beforeEach(function(done) {
|
|
|
|
|
+ const oneHourInSeconds = 60 * 60;
|
|
|
|
|
+ const oneHourInMilliseconds = oneHourInSeconds * 1000;
|
|
|
|
|
+ const nowInSeconds = Date.now()/1000;
|
|
|
|
|
+ this.containers = [{
|
|
|
|
|
+ Name: "/project-old-container-name",
|
|
|
|
|
+ Id: "old-container-id",
|
|
|
Created: nowInSeconds - oneHourInSeconds - 100
|
|
Created: nowInSeconds - oneHourInSeconds - 100
|
|
|
}, {
|
|
}, {
|
|
|
- Name: "/project-new-container-name"
|
|
|
|
|
- Id: "new-container-id"
|
|
|
|
|
- Created: nowInSeconds - oneHourInSeconds + 100
|
|
|
|
|
|
|
+ Name: "/project-new-container-name",
|
|
|
|
|
+ Id: "new-container-id",
|
|
|
|
|
+ Created: (nowInSeconds - oneHourInSeconds) + 100
|
|
|
}, {
|
|
}, {
|
|
|
- Name: "/totally-not-a-project-container"
|
|
|
|
|
- Id: "some-random-id"
|
|
|
|
|
|
|
+ Name: "/totally-not-a-project-container",
|
|
|
|
|
+ Id: "some-random-id",
|
|
|
Created: nowInSeconds - (2 * oneHourInSeconds )
|
|
Created: nowInSeconds - (2 * oneHourInSeconds )
|
|
|
- }]
|
|
|
|
|
- @DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds
|
|
|
|
|
- @listContainers.callsArgWith(1, null, @containers)
|
|
|
|
|
- @DockerRunner.destroyContainer = sinon.stub().callsArg(3)
|
|
|
|
|
- @DockerRunner.destroyOldContainers (error) =>
|
|
|
|
|
- @callback(error)
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- it "should list all containers", ->
|
|
|
|
|
- @listContainers
|
|
|
|
|
- .calledWith(all: true)
|
|
|
|
|
- .should.equal true
|
|
|
|
|
-
|
|
|
|
|
- it "should destroy old containers", ->
|
|
|
|
|
- @DockerRunner.destroyContainer
|
|
|
|
|
|
|
+ }];
|
|
|
|
|
+ this.DockerRunner.MAX_CONTAINER_AGE = oneHourInMilliseconds;
|
|
|
|
|
+ this.listContainers.callsArgWith(1, null, this.containers);
|
|
|
|
|
+ this.DockerRunner.destroyContainer = sinon.stub().callsArg(3);
|
|
|
|
|
+ return this.DockerRunner.destroyOldContainers(error => {
|
|
|
|
|
+ this.callback(error);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should list all containers", function() {
|
|
|
|
|
+ return this.listContainers
|
|
|
|
|
+ .calledWith({all: true})
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it("should destroy old containers", function() {
|
|
|
|
|
+ this.DockerRunner.destroyContainer
|
|
|
.callCount
|
|
.callCount
|
|
|
- .should.equal 1
|
|
|
|
|
- @DockerRunner.destroyContainer
|
|
|
|
|
|
|
+ .should.equal(1);
|
|
|
|
|
+ return this.DockerRunner.destroyContainer
|
|
|
.calledWith("/project-old-container-name", "old-container-id")
|
|
.calledWith("/project-old-container-name", "old-container-id")
|
|
|
- .should.equal true
|
|
|
|
|
|
|
+ .should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- it "should not destroy new containers", ->
|
|
|
|
|
- @DockerRunner.destroyContainer
|
|
|
|
|
|
|
+ it("should not destroy new containers", function() {
|
|
|
|
|
+ return this.DockerRunner.destroyContainer
|
|
|
.calledWith("/project-new-container-name", "new-container-id")
|
|
.calledWith("/project-new-container-name", "new-container-id")
|
|
|
- .should.equal false
|
|
|
|
|
|
|
+ .should.equal(false);
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- it "should not destroy non-project containers", ->
|
|
|
|
|
- @DockerRunner.destroyContainer
|
|
|
|
|
|
|
+ it("should not destroy non-project containers", function() {
|
|
|
|
|
+ return this.DockerRunner.destroyContainer
|
|
|
.calledWith("/totally-not-a-project-container", "some-random-id")
|
|
.calledWith("/totally-not-a-project-container", "some-random-id")
|
|
|
- .should.equal false
|
|
|
|
|
-
|
|
|
|
|
- it "should callback the callback", ->
|
|
|
|
|
- @callback.called.should.equal true
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- describe '_destroyContainer', ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @containerId = 'some_id'
|
|
|
|
|
- @fakeContainer =
|
|
|
|
|
- remove: sinon.stub().callsArgWith(1, null)
|
|
|
|
|
- @Docker::getContainer = sinon.stub().returns(@fakeContainer)
|
|
|
|
|
-
|
|
|
|
|
- it 'should get the container', (done) ->
|
|
|
|
|
- @DockerRunner._destroyContainer @containerId, false, (err) =>
|
|
|
|
|
- @Docker::getContainer.callCount.should.equal 1
|
|
|
|
|
- @Docker::getContainer.calledWith(@containerId).should.equal true
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- it 'should try to force-destroy the container when shouldForce=true', (done) ->
|
|
|
|
|
- @DockerRunner._destroyContainer @containerId, true, (err) =>
|
|
|
|
|
- @fakeContainer.remove.callCount.should.equal 1
|
|
|
|
|
- @fakeContainer.remove.calledWith({force: true}).should.equal true
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- it 'should not try to force-destroy the container when shouldForce=false', (done) ->
|
|
|
|
|
- @DockerRunner._destroyContainer @containerId, false, (err) =>
|
|
|
|
|
- @fakeContainer.remove.callCount.should.equal 1
|
|
|
|
|
- @fakeContainer.remove.calledWith({force: false}).should.equal true
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- it 'should not produce an error', (done) ->
|
|
|
|
|
- @DockerRunner._destroyContainer @containerId, false, (err) =>
|
|
|
|
|
- expect(err).to.equal null
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- describe 'when the container is already gone', ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @fakeError = new Error('woops')
|
|
|
|
|
- @fakeError.statusCode = 404
|
|
|
|
|
- @fakeContainer =
|
|
|
|
|
- remove: sinon.stub().callsArgWith(1, @fakeError)
|
|
|
|
|
- @Docker::getContainer = sinon.stub().returns(@fakeContainer)
|
|
|
|
|
-
|
|
|
|
|
- it 'should not produce an error', (done) ->
|
|
|
|
|
- @DockerRunner._destroyContainer @containerId, false, (err) =>
|
|
|
|
|
- expect(err).to.equal null
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- describe 'when container.destroy produces an error', (done) ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @fakeError = new Error('woops')
|
|
|
|
|
- @fakeError.statusCode = 500
|
|
|
|
|
- @fakeContainer =
|
|
|
|
|
- remove: sinon.stub().callsArgWith(1, @fakeError)
|
|
|
|
|
- @Docker::getContainer = sinon.stub().returns(@fakeContainer)
|
|
|
|
|
-
|
|
|
|
|
- it 'should produce an error', (done) ->
|
|
|
|
|
- @DockerRunner._destroyContainer @containerId, false, (err) =>
|
|
|
|
|
- expect(err).to.not.equal null
|
|
|
|
|
- expect(err).to.equal @fakeError
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- describe 'kill', ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @containerId = 'some_id'
|
|
|
|
|
- @fakeContainer =
|
|
|
|
|
- kill: sinon.stub().callsArgWith(0, null)
|
|
|
|
|
- @Docker::getContainer = sinon.stub().returns(@fakeContainer)
|
|
|
|
|
-
|
|
|
|
|
- it 'should get the container', (done) ->
|
|
|
|
|
- @DockerRunner.kill @containerId, (err) =>
|
|
|
|
|
- @Docker::getContainer.callCount.should.equal 1
|
|
|
|
|
- @Docker::getContainer.calledWith(@containerId).should.equal true
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- it 'should try to force-destroy the container', (done) ->
|
|
|
|
|
- @DockerRunner.kill @containerId, (err) =>
|
|
|
|
|
- @fakeContainer.kill.callCount.should.equal 1
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- it 'should not produce an error', (done) ->
|
|
|
|
|
- @DockerRunner.kill @containerId, (err) =>
|
|
|
|
|
- expect(err).to.equal undefined
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- describe 'when the container is not actually running', ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @fakeError = new Error('woops')
|
|
|
|
|
- @fakeError.statusCode = 500
|
|
|
|
|
- @fakeError.message = 'Cannot kill container <whatever> is not running'
|
|
|
|
|
- @fakeContainer =
|
|
|
|
|
- kill: sinon.stub().callsArgWith(0, @fakeError)
|
|
|
|
|
- @Docker::getContainer = sinon.stub().returns(@fakeContainer)
|
|
|
|
|
-
|
|
|
|
|
- it 'should not produce an error', (done) ->
|
|
|
|
|
- @DockerRunner.kill @containerId, (err) =>
|
|
|
|
|
- expect(err).to.equal undefined
|
|
|
|
|
- done()
|
|
|
|
|
-
|
|
|
|
|
- describe 'when container.kill produces a legitimate error', (done) ->
|
|
|
|
|
- beforeEach ->
|
|
|
|
|
- @fakeError = new Error('woops')
|
|
|
|
|
- @fakeError.statusCode = 500
|
|
|
|
|
- @fakeError.message = 'Totally legitimate reason to throw an error'
|
|
|
|
|
- @fakeContainer =
|
|
|
|
|
- kill: sinon.stub().callsArgWith(0, @fakeError)
|
|
|
|
|
- @Docker::getContainer = sinon.stub().returns(@fakeContainer)
|
|
|
|
|
-
|
|
|
|
|
- it 'should produce an error', (done) ->
|
|
|
|
|
- @DockerRunner.kill @containerId, (err) =>
|
|
|
|
|
- expect(err).to.not.equal undefined
|
|
|
|
|
- expect(err).to.equal @fakeError
|
|
|
|
|
- done()
|
|
|
|
|
|
|
+ .should.equal(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it("should callback the callback", function() {
|
|
|
|
|
+ return this.callback.called.should.equal(true);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ describe('_destroyContainer', function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.containerId = 'some_id';
|
|
|
|
|
+ this.fakeContainer =
|
|
|
|
|
+ {remove: sinon.stub().callsArgWith(1, null)};
|
|
|
|
|
+ return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('should get the container', function(done) {
|
|
|
|
|
+ return this.DockerRunner._destroyContainer(this.containerId, false, err => {
|
|
|
|
|
+ this.Docker.prototype.getContainer.callCount.should.equal(1);
|
|
|
|
|
+ this.Docker.prototype.getContainer.calledWith(this.containerId).should.equal(true);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('should try to force-destroy the container when shouldForce=true', function(done) {
|
|
|
|
|
+ return this.DockerRunner._destroyContainer(this.containerId, true, err => {
|
|
|
|
|
+ this.fakeContainer.remove.callCount.should.equal(1);
|
|
|
|
|
+ this.fakeContainer.remove.calledWith({force: true}).should.equal(true);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('should not try to force-destroy the container when shouldForce=false', function(done) {
|
|
|
|
|
+ return this.DockerRunner._destroyContainer(this.containerId, false, err => {
|
|
|
|
|
+ this.fakeContainer.remove.callCount.should.equal(1);
|
|
|
|
|
+ this.fakeContainer.remove.calledWith({force: false}).should.equal(true);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('should not produce an error', function(done) {
|
|
|
|
|
+ return this.DockerRunner._destroyContainer(this.containerId, false, err => {
|
|
|
|
|
+ expect(err).to.equal(null);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe('when the container is already gone', function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.fakeError = new Error('woops');
|
|
|
|
|
+ this.fakeError.statusCode = 404;
|
|
|
|
|
+ this.fakeContainer =
|
|
|
|
|
+ {remove: sinon.stub().callsArgWith(1, this.fakeError)};
|
|
|
|
|
+ return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it('should not produce an error', function(done) {
|
|
|
|
|
+ return this.DockerRunner._destroyContainer(this.containerId, false, err => {
|
|
|
|
|
+ expect(err).to.equal(null);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return describe('when container.destroy produces an error', function(done) {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.fakeError = new Error('woops');
|
|
|
|
|
+ this.fakeError.statusCode = 500;
|
|
|
|
|
+ this.fakeContainer =
|
|
|
|
|
+ {remove: sinon.stub().callsArgWith(1, this.fakeError)};
|
|
|
|
|
+ return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it('should produce an error', function(done) {
|
|
|
|
|
+ return this.DockerRunner._destroyContainer(this.containerId, false, err => {
|
|
|
|
|
+ expect(err).to.not.equal(null);
|
|
|
|
|
+ expect(err).to.equal(this.fakeError);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ return describe('kill', function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.containerId = 'some_id';
|
|
|
|
|
+ this.fakeContainer =
|
|
|
|
|
+ {kill: sinon.stub().callsArgWith(0, null)};
|
|
|
|
|
+ return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('should get the container', function(done) {
|
|
|
|
|
+ return this.DockerRunner.kill(this.containerId, err => {
|
|
|
|
|
+ this.Docker.prototype.getContainer.callCount.should.equal(1);
|
|
|
|
|
+ this.Docker.prototype.getContainer.calledWith(this.containerId).should.equal(true);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('should try to force-destroy the container', function(done) {
|
|
|
|
|
+ return this.DockerRunner.kill(this.containerId, err => {
|
|
|
|
|
+ this.fakeContainer.kill.callCount.should.equal(1);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('should not produce an error', function(done) {
|
|
|
|
|
+ return this.DockerRunner.kill(this.containerId, err => {
|
|
|
|
|
+ expect(err).to.equal(undefined);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe('when the container is not actually running', function() {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.fakeError = new Error('woops');
|
|
|
|
|
+ this.fakeError.statusCode = 500;
|
|
|
|
|
+ this.fakeError.message = 'Cannot kill container <whatever> is not running';
|
|
|
|
|
+ this.fakeContainer =
|
|
|
|
|
+ {kill: sinon.stub().callsArgWith(0, this.fakeError)};
|
|
|
|
|
+ return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it('should not produce an error', function(done) {
|
|
|
|
|
+ return this.DockerRunner.kill(this.containerId, err => {
|
|
|
|
|
+ expect(err).to.equal(undefined);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return describe('when container.kill produces a legitimate error', function(done) {
|
|
|
|
|
+ beforeEach(function() {
|
|
|
|
|
+ this.fakeError = new Error('woops');
|
|
|
|
|
+ this.fakeError.statusCode = 500;
|
|
|
|
|
+ this.fakeError.message = 'Totally legitimate reason to throw an error';
|
|
|
|
|
+ this.fakeContainer =
|
|
|
|
|
+ {kill: sinon.stub().callsArgWith(0, this.fakeError)};
|
|
|
|
|
+ return this.Docker.prototype.getContainer = sinon.stub().returns(this.fakeContainer);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return it('should produce an error', function(done) {
|
|
|
|
|
+ return this.DockerRunner.kill(this.containerId, err => {
|
|
|
|
|
+ expect(err).to.not.equal(undefined);
|
|
|
|
|
+ expect(err).to.equal(this.fakeError);
|
|
|
|
|
+ return done();
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+});
|