mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 19:03:39 -05:00
1ddf9283f2
Adds a `flags` parameter to the request JSON, appearing under the `compile.options` key (alongside such stalwarts as `compiler`, `timeout`, etc.). This is primarily to support `-file-line-error` as an option, but could have other uses as well. `flags` should be an array of strings, or absent. If supplied, the listed arguments are added to the base latexmk command.
356 lines
11 KiB
CoffeeScript
356 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"
|
|
flags: @flags = ["-file-line-error"]
|
|
@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
|
|
flags: @flags
|
|
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
|
|
flags: @flags
|
|
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
|
|
flags: @flags
|
|
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}"
|
|
@CommandRunner.run
|
|
.calledWith(
|
|
"#{@project_id}-#{@user_id}",
|
|
['/opt/synctex', 'code', synctex_path, file_path, @line, @column],
|
|
"#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}",
|
|
@Settings.clsi.docker.image,
|
|
60000,
|
|
{}
|
|
).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(
|
|
"#{@project_id}-#{@user_id}",
|
|
['/opt/synctex', "pdf", synctex_path, @page, @h, @v],
|
|
"#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}",
|
|
@Settings.clsi.docker.image,
|
|
60000,
|
|
{}).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 = 60 * 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
|