overleaf/services/clsi/test/unit/js/LatexRunnerTests.js
Antoine Clausse 7f48c67512 Add prefer-node-protocol ESLint rule (#21532)
* Add `unicorn/prefer-node-protocol`

* Fix `unicorn/prefer-node-protocol` ESLint errors

* Run `npm run format:fix`

* Add sandboxed-module sourceTransformers in mocha setups

Fix `no such file or directory, open 'node:fs'` in `sandboxed-module`

* Remove `node:` in the SandboxedModule requires

* Fix new linting errors with `node:`

GitOrigin-RevId: 68f6e31e2191fcff4cb8058dd0a6914c14f59926
2024-11-11 09:04:51 +00:00

219 lines
6.2 KiB
JavaScript

const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { expect } = require('chai')
const MODULE_PATH = require('node:path').join(
__dirname,
'../../../app/js/LatexRunner'
)
describe('LatexRunner', function () {
beforeEach(function () {
this.Settings = {
docker: {
socketPath: '/var/run/docker.sock',
},
}
this.commandRunnerOutput = {
stdout: 'this is stdout',
stderr: 'this is stderr',
}
this.CommandRunner = {
run: sinon.stub().yields(null, this.commandRunnerOutput),
}
this.fs = {
writeFile: sinon.stub().yields(),
unlink: sinon
.stub()
.yields(new Error('ENOENT: no such file or directory, unlink ...')),
}
this.LatexRunner = SandboxedModule.require(MODULE_PATH, {
requires: {
'@overleaf/settings': this.Settings,
'./CommandRunner': this.CommandRunner,
fs: this.fs,
},
})
this.directory = '/local/compile/directory'
this.mainFile = 'main-file.tex'
this.compiler = 'pdflatex'
this.image = 'example.com/image'
this.compileGroup = 'compile-group'
this.callback = sinon.stub()
this.project_id = 'project-id-123'
this.env = { foo: '123' }
this.timeout = 42000
this.flags = []
this.stopOnFirstError = false
this.stats = {}
this.timings = {}
this.call = function (callback) {
this.LatexRunner.runLatex(
this.project_id,
{
directory: this.directory,
mainFile: this.mainFile,
compiler: this.compiler,
timeout: this.timeout,
image: this.image,
environment: this.env,
compileGroup: this.compileGroup,
flags: this.flags,
stopOnFirstError: this.stopOnFirstError,
timings: this.timings,
stats: this.stats,
},
callback
)
}
})
describe('runLatex', function () {
describe('normally', function () {
beforeEach(function (done) {
this.call(done)
})
it('should run the latex command', function () {
this.CommandRunner.run.should.have.been.calledWith(
this.project_id,
[
'latexmk',
'-cd',
'-jobname=output',
'-auxdir=$COMPILE_DIR',
'-outdir=$COMPILE_DIR',
'-synctex=1',
'-interaction=batchmode',
'-f',
'-pdf',
'$COMPILE_DIR/main-file.tex',
],
this.directory,
this.image,
this.timeout,
this.env,
this.compileGroup
)
})
it('should record the stdout and stderr', function () {
this.fs.writeFile.should.have.been.calledWith(
this.directory + '/' + 'output.stdout',
'this is stdout',
{ flag: 'wx' }
)
this.fs.writeFile.should.have.been.calledWith(
this.directory + '/' + 'output.stderr',
'this is stderr',
{ flag: 'wx' }
)
this.fs.unlink.should.have.been.calledWith(
this.directory + '/' + 'output.stdout'
)
this.fs.unlink.should.have.been.calledWith(
this.directory + '/' + 'output.stderr'
)
})
it('should not record cpu metrics', function () {
expect(this.timings['cpu-percent']).to.not.exist
expect(this.timings['cpu-time']).to.not.exist
expect(this.timings['sys-time']).to.not.exist
})
})
describe('with a different compiler', function () {
beforeEach(function (done) {
this.compiler = 'lualatex'
this.call(done)
})
it('should set the appropriate latexmk flag', function () {
this.CommandRunner.run.should.have.been.calledWith(this.project_id, [
'latexmk',
'-cd',
'-jobname=output',
'-auxdir=$COMPILE_DIR',
'-outdir=$COMPILE_DIR',
'-synctex=1',
'-interaction=batchmode',
'-f',
'-lualatex',
'$COMPILE_DIR/main-file.tex',
])
})
})
describe('with time -v', function () {
beforeEach(function (done) {
this.commandRunnerOutput.stderr =
'\tCommand being timed: "sh -c timeout 1 yes > /dev/null"\n' +
'\tUser time (seconds): 0.28\n' +
'\tSystem time (seconds): 0.70\n' +
'\tPercent of CPU this job got: 98%\n'
this.call(done)
})
it('should record cpu metrics', function () {
expect(this.timings['cpu-percent']).to.equal(98)
expect(this.timings['cpu-time']).to.equal(0.28)
expect(this.timings['sys-time']).to.equal(0.7)
})
})
describe('with an .Rtex main file', function () {
beforeEach(function (done) {
this.mainFile = 'main-file.Rtex'
this.call(done)
})
it('should run the latex command on the equivalent .tex file', function () {
const command = this.CommandRunner.run.args[0][1]
const mainFile = command.slice(-1)[0]
mainFile.should.equal('$COMPILE_DIR/main-file.tex')
})
})
describe('with a flags option', function () {
beforeEach(function (done) {
this.flags = ['-shell-restricted', '-halt-on-error']
this.call(done)
})
it('should include the flags in the command', function () {
const command = this.CommandRunner.run.args[0][1]
const flags = command.filter(
arg => arg === '-shell-restricted' || arg === '-halt-on-error'
)
flags.length.should.equal(2)
flags[0].should.equal('-shell-restricted')
flags[1].should.equal('-halt-on-error')
})
})
describe('with the stopOnFirstError option', function () {
beforeEach(function (done) {
this.stopOnFirstError = true
this.call(done)
})
it('should set the appropriate flags', function () {
this.CommandRunner.run.should.have.been.calledWith(this.project_id, [
'latexmk',
'-cd',
'-jobname=output',
'-auxdir=$COMPILE_DIR',
'-outdir=$COMPILE_DIR',
'-synctex=1',
'-interaction=batchmode',
'-halt-on-error',
'-pdf',
'$COMPILE_DIR/main-file.tex',
])
})
})
})
})