overleaf/services/clsi/test/unit/coffee/CompileManagerTests.coffee
Henry Oswald 9a519f0d3d added docker runner into core codebase
supports both local command runner and docker runner

added docker files for tex live

also fixed tests so they exit correctly & removed debug lines
2018-03-14 15:44:49 +00:00

341 lines
11 KiB
CoffeeScript

SandboxedModule = require('sandboxed-module')
sinon = require('sinon')
require('chai').should()
modulePath = require('path').join __dirname, '../../../app/js/CompileManager'
tk = require("timekeeper")
EventEmitter = require("events").EventEmitter
Path = require "path"
describe "CompileManager", ->
beforeEach ->
@CompileManager = SandboxedModule.require modulePath, requires:
"./LatexRunner": @LatexRunner = {}
"./ResourceWriter": @ResourceWriter = {}
"./OutputFileFinder": @OutputFileFinder = {}
"./OutputCacheManager": @OutputCacheManager = {}
"settings-sharelatex": @Settings =
path:
compilesDir: "/compiles/dir"
synctexBaseDir: -> "/compile"
clsi:
docker:
image: "SOMEIMAGE"
"logger-sharelatex": @logger = { log: sinon.stub() , info:->}
"child_process": @child_process = {}
"./CommandRunner": @CommandRunner = {}
"./DraftModeManager": @DraftModeManager = {}
"./TikzManager": @TikzManager = {}
"./LockManager": @LockManager = {}
"fs": @fs = {}
"fs-extra": @fse = { ensureDir: sinon.stub().callsArg(1) }
@callback = sinon.stub()
@project_id = "project-id-123"
@user_id = "1234"
describe "doCompileWithLock", ->
beforeEach ->
@request =
resources: @resources = "mock-resources"
project_id: @project_id
user_id: @user_id
@output_files = ["foo", "bar"]
@Settings.compileDir = "compiles"
@compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
@CompileManager.doCompile = sinon.stub().callsArgWith(1, null, @output_files)
@LockManager.runWithLock = (lockFile, runner, callback) ->
runner (err, result...) ->
callback(err, result...)
describe "when the project is not locked", ->
beforeEach ->
@CompileManager.doCompileWithLock @request, @callback
it "should ensure that the compile directory exists", ->
@fse.ensureDir.calledWith(@compileDir)
.should.equal true
it "should call doCompile with the request", ->
@CompileManager.doCompile
.calledWith(@request)
.should.equal true
it "should call the callback with the output files", ->
@callback.calledWithExactly(null, @output_files)
.should.equal true
describe "when the project is locked", ->
beforeEach ->
@error = new Error("locked")
@LockManager.runWithLock = (lockFile, runner, callback) =>
callback(@error)
@CompileManager.doCompileWithLock @request, @callback
it "should ensure that the compile directory exists", ->
@fse.ensureDir.calledWith(@compileDir)
.should.equal true
it "should not call doCompile with the request", ->
@CompileManager.doCompile
.called.should.equal false
it "should call the callback with the error", ->
@callback.calledWithExactly(@error)
.should.equal true
describe "doCompile", ->
beforeEach ->
@output_files = [{
path: "output.log"
type: "log"
}, {
path: "output.pdf"
type: "pdf"
}]
@build_files = [{
path: "output.log"
type: "log"
build: 1234
}, {
path: "output.pdf"
type: "pdf"
build: 1234
}]
@request =
resources: @resources = "mock-resources"
rootResourcePath: @rootResourcePath = "main.tex"
project_id: @project_id
user_id: @user_id
compiler: @compiler = "pdflatex"
timeout: @timeout = 42000
imageName: @image = "example.com/image"
@env = {}
@Settings.compileDir = "compiles"
@compileDir = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
@ResourceWriter.syncResourcesToDisk = sinon.stub().callsArgWith(2, null, @resources)
@LatexRunner.runLatex = sinon.stub().callsArg(2)
@OutputFileFinder.findOutputFiles = sinon.stub().callsArgWith(2, null, @output_files)
@OutputCacheManager.saveOutputFiles = sinon.stub().callsArgWith(2, null, @build_files)
@DraftModeManager.injectDraftMode = sinon.stub().callsArg(1)
@TikzManager.checkMainFile = sinon.stub().callsArg(3, false)
describe "normally", ->
beforeEach ->
@CompileManager.doCompile @request, @callback
it "should write the resources to disk", ->
@ResourceWriter.syncResourcesToDisk
.calledWith(@request, @compileDir)
.should.equal true
it "should run LaTeX", ->
@LatexRunner.runLatex
.calledWith("#{@project_id}-#{@user_id}", {
directory: @compileDir
mainFile: @rootResourcePath
compiler: @compiler
timeout: @timeout
image: @image
environment: @env
})
.should.equal true
it "should find the output files", ->
@OutputFileFinder.findOutputFiles
.calledWith(@resources, @compileDir)
.should.equal true
it "should return the output files", ->
@callback.calledWith(null, @build_files).should.equal true
it "should not inject draft mode by default", ->
@DraftModeManager.injectDraftMode.called.should.equal false
describe "with draft mode", ->
beforeEach ->
@request.draft = true
@CompileManager.doCompile @request, @callback
it "should inject the draft mode header", ->
@DraftModeManager.injectDraftMode
.calledWith(@compileDir + "/" + @rootResourcePath)
.should.equal true
describe "with a check option", ->
beforeEach ->
@request.check = "error"
@CompileManager.doCompile @request, @callback
it "should run chktex", ->
@LatexRunner.runLatex
.calledWith("#{@project_id}-#{@user_id}", {
directory: @compileDir
mainFile: @rootResourcePath
compiler: @compiler
timeout: @timeout
image: @image
environment: {'CHKTEX_OPTIONS': '-nall -e9 -e10 -w15 -w16', 'CHKTEX_EXIT_ON_ERROR':1, 'CHKTEX_ULIMIT_OPTIONS': '-t 5 -v 64000'}
})
.should.equal true
describe "with a knitr file and check options", ->
beforeEach ->
@request.rootResourcePath = "main.Rtex"
@request.check = "error"
@CompileManager.doCompile @request, @callback
it "should not run chktex", ->
@LatexRunner.runLatex
.calledWith("#{@project_id}-#{@user_id}", {
directory: @compileDir
mainFile: "main.Rtex"
compiler: @compiler
timeout: @timeout
image: @image
environment: @env
})
.should.equal true
describe "clearProject", ->
describe "succesfully", ->
beforeEach ->
@Settings.compileDir = "compiles"
@fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory: ()->true})
@proc = new EventEmitter()
@proc.stdout = new EventEmitter()
@proc.stderr = new EventEmitter()
@child_process.spawn = sinon.stub().returns(@proc)
@CompileManager.clearProject @project_id, @user_id, @callback
@proc.emit "close", 0
it "should remove the project directory", ->
@child_process.spawn
.calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"])
.should.equal true
it "should call the callback", ->
@callback.called.should.equal true
describe "with a non-success status code", ->
beforeEach ->
@Settings.compileDir = "compiles"
@fs.lstat = sinon.stub().callsArgWith(1, null,{isDirectory: ()->true})
@proc = new EventEmitter()
@proc.stdout = new EventEmitter()
@proc.stderr = new EventEmitter()
@child_process.spawn = sinon.stub().returns(@proc)
@CompileManager.clearProject @project_id, @user_id, @callback
@proc.stderr.emit "data", @error = "oops"
@proc.emit "close", 1
it "should remove the project directory", ->
@child_process.spawn
.calledWith("rm", ["-r", "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"])
.should.equal true
it "should call the callback with an error from the stderr", ->
@callback
.calledWith(new Error())
.should.equal true
@callback.args[0][0].message.should.equal "rm -r #{@Settings.path.compilesDir}/#{@project_id}-#{@user_id} failed: #{@error}"
describe "syncing", ->
beforeEach ->
@page = 1
@h = 42.23
@v = 87.56
@width = 100.01
@height = 234.56
@line = 5
@column = 3
@file_name = "main.tex"
@child_process.execFile = sinon.stub()
@Settings.path.synctexBaseDir = (project_id) => "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
describe "syncFromCode", ->
beforeEach ->
@fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true})
@stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n"
@CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout})
@CompileManager.syncFromCode @project_id, @user_id, @file_name, @line, @column, @callback
# it "should execute the synctex binary", ->
# bin_path = Path.resolve(__dirname + "/../../../bin/synctex")
# synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf"
# file_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}"
# @child_process.execFile
# .calledWith(bin_path, ["code", synctex_path, file_path, @line, @column], timeout: 10000)
# .should.equal true
it "should call the callback with the parsed output", ->
@callback
.calledWith(null, [{
page: @page
h: @h
v: @v
height: @height
width: @width
}])
.should.equal true
describe "syncFromPdf", ->
beforeEach ->
@fs.stat = sinon.stub().callsArgWith(1, null,{isFile: ()->true})
@stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}\t#{@line}\t#{@column}\n"
@CommandRunner.run = sinon.stub().callsArgWith(6, null, {stdout:@stdout})
@CompileManager.syncFromPdf @project_id, @user_id, @page, @h, @v, @callback
# it "should execute the synctex binary", ->
# bin_path = Path.resolve(__dirname + "/../../../bin/synctex")
# synctex_path = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/output.pdf"
# @CommandRunner.run
# .calledWith(bin_path, ["pdf", synctex_path, @page, @h, @v], timeout: 10000)
# .should.equal true
it "should call the callback with the parsed output", ->
@callback
.calledWith(null, [{
file: @file_name
line: @line
column: @column
}])
.should.equal true
describe "wordcount", ->
beforeEach ->
@CommandRunner.run = sinon.stub().callsArg(6)
@fs.readFile = sinon.stub().callsArgWith(2, null, @stdout = "Encoding: ascii\nWords in text: 2")
@callback = sinon.stub()
@project_id
@timeout = 10 * 1000
@file_name = "main.tex"
@Settings.path.compilesDir = "/local/compile/directory"
@image = "example.com/image"
@CompileManager.wordcount @project_id, @user_id, @file_name, @image, @callback
it "should run the texcount command", ->
@directory = "#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}"
@file_path = "$COMPILE_DIR/#{@file_name}"
@command =[ "texcount", "-nocol", "-inc", @file_path, "-out=" + @file_path + ".wc"]
@CommandRunner.run
.calledWith("#{@project_id}-#{@user_id}", @command, @directory, @image, @timeout, {})
.should.equal true
it "should call the callback with the parsed output", ->
@callback
.calledWith(null, {
encode: "ascii"
textWords: 2
headWords: 0
outside: 0
headers: 0
elements: 0
mathInline: 0
mathDisplay: 0
errors: 0
messages: ""
})
.should.equal true