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" } "logger-sharelatex": @logger = { log: sinon.stub() , info:->} "child_process": @child_process = {} "./CommandRunner": @CommandRunner = {} "./DraftModeManager": @DraftModeManager = {} "fs": @fs = {} @callback = sinon.stub() 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 = "project-id-123" user_id: @user_id = "1234" 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().callsArg(3) @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) describe "normally", -> beforeEach -> @CompileManager.doCompile @request, @callback it "should write the resources to disk", -> @ResourceWriter.syncResourcesToDisk .calledWith(@project_id, @resources, @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} }) .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 -> @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@page}\t#{@h}\t#{@v}\t#{@width}\t#{@height}\n", "") @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 -> @child_process.execFile.callsArgWith(3, null, @stdout = "NODE\t#{@Settings.path.compilesDir}/#{@project_id}-#{@user_id}/#{@file_name}\t#{@line}\t#{@column}\n", "") @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" @child_process.execFile .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 = "project-id-123" @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