LatexRunnerTests.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. const SandboxedModule = require('sandboxed-module')
  2. const sinon = require('sinon')
  3. const { expect } = require('chai')
  4. const MODULE_PATH = require('node:path').join(
  5. __dirname,
  6. '../../../app/js/LatexRunner'
  7. )
  8. describe('LatexRunner', function () {
  9. beforeEach(function () {
  10. this.Settings = {
  11. docker: {
  12. socketPath: '/var/run/docker.sock',
  13. },
  14. }
  15. this.commandRunnerOutput = {
  16. stdout: 'this is stdout',
  17. stderr: 'this is stderr',
  18. }
  19. this.CommandRunner = {
  20. run: sinon.stub().yields(null, this.commandRunnerOutput),
  21. }
  22. this.fs = {
  23. writeFile: sinon.stub().yields(),
  24. unlink: sinon
  25. .stub()
  26. .yields(new Error('ENOENT: no such file or directory, unlink ...')),
  27. }
  28. this.LatexRunner = SandboxedModule.require(MODULE_PATH, {
  29. requires: {
  30. '@overleaf/settings': this.Settings,
  31. './CommandRunner': this.CommandRunner,
  32. fs: this.fs,
  33. },
  34. })
  35. this.directory = '/local/compile/directory'
  36. this.mainFile = 'main-file.tex'
  37. this.compiler = 'pdflatex'
  38. this.image = 'example.com/image'
  39. this.compileGroup = 'compile-group'
  40. this.callback = sinon.stub()
  41. this.project_id = 'project-id-123'
  42. this.env = { foo: '123' }
  43. this.timeout = 42000
  44. this.flags = []
  45. this.stopOnFirstError = false
  46. this.stats = {}
  47. this.timings = {}
  48. this.call = function (callback) {
  49. this.LatexRunner.runLatex(
  50. this.project_id,
  51. {
  52. directory: this.directory,
  53. mainFile: this.mainFile,
  54. compiler: this.compiler,
  55. timeout: this.timeout,
  56. image: this.image,
  57. environment: this.env,
  58. compileGroup: this.compileGroup,
  59. flags: this.flags,
  60. stopOnFirstError: this.stopOnFirstError,
  61. timings: this.timings,
  62. stats: this.stats,
  63. },
  64. callback
  65. )
  66. }
  67. })
  68. describe('runLatex', function () {
  69. describe('normally', function () {
  70. beforeEach(function (done) {
  71. this.call(done)
  72. })
  73. it('should run the latex command', function () {
  74. this.CommandRunner.run.should.have.been.calledWith(
  75. this.project_id,
  76. [
  77. 'latexmk',
  78. '-cd',
  79. '-jobname=output',
  80. '-auxdir=$COMPILE_DIR',
  81. '-outdir=$COMPILE_DIR',
  82. '-synctex=1',
  83. '-interaction=batchmode',
  84. '-f',
  85. '-pdf',
  86. '$COMPILE_DIR/main-file.tex',
  87. ],
  88. this.directory,
  89. this.image,
  90. this.timeout,
  91. this.env,
  92. this.compileGroup
  93. )
  94. })
  95. it('should record the stdout and stderr', function () {
  96. this.fs.writeFile.should.have.been.calledWith(
  97. this.directory + '/' + 'output.stdout',
  98. 'this is stdout',
  99. { flag: 'wx' }
  100. )
  101. this.fs.writeFile.should.have.been.calledWith(
  102. this.directory + '/' + 'output.stderr',
  103. 'this is stderr',
  104. { flag: 'wx' }
  105. )
  106. this.fs.unlink.should.have.been.calledWith(
  107. this.directory + '/' + 'output.stdout'
  108. )
  109. this.fs.unlink.should.have.been.calledWith(
  110. this.directory + '/' + 'output.stderr'
  111. )
  112. })
  113. it('should not record cpu metrics', function () {
  114. expect(this.timings['cpu-percent']).to.not.exist
  115. expect(this.timings['cpu-time']).to.not.exist
  116. expect(this.timings['sys-time']).to.not.exist
  117. })
  118. })
  119. describe('with a different compiler', function () {
  120. beforeEach(function (done) {
  121. this.compiler = 'lualatex'
  122. this.call(done)
  123. })
  124. it('should set the appropriate latexmk flag', function () {
  125. this.CommandRunner.run.should.have.been.calledWith(this.project_id, [
  126. 'latexmk',
  127. '-cd',
  128. '-jobname=output',
  129. '-auxdir=$COMPILE_DIR',
  130. '-outdir=$COMPILE_DIR',
  131. '-synctex=1',
  132. '-interaction=batchmode',
  133. '-f',
  134. '-lualatex',
  135. '$COMPILE_DIR/main-file.tex',
  136. ])
  137. })
  138. })
  139. describe('with time -v', function () {
  140. beforeEach(function (done) {
  141. this.commandRunnerOutput.stderr =
  142. '\tCommand being timed: "sh -c timeout 1 yes > /dev/null"\n' +
  143. '\tUser time (seconds): 0.28\n' +
  144. '\tSystem time (seconds): 0.70\n' +
  145. '\tPercent of CPU this job got: 98%\n'
  146. this.call(done)
  147. })
  148. it('should record cpu metrics', function () {
  149. expect(this.timings['cpu-percent']).to.equal(98)
  150. expect(this.timings['cpu-time']).to.equal(0.28)
  151. expect(this.timings['sys-time']).to.equal(0.7)
  152. })
  153. })
  154. describe('with an .Rtex main file', function () {
  155. beforeEach(function (done) {
  156. this.mainFile = 'main-file.Rtex'
  157. this.call(done)
  158. })
  159. it('should run the latex command on the equivalent .tex file', function () {
  160. const command = this.CommandRunner.run.args[0][1]
  161. const mainFile = command.slice(-1)[0]
  162. mainFile.should.equal('$COMPILE_DIR/main-file.tex')
  163. })
  164. })
  165. describe('with a flags option', function () {
  166. beforeEach(function (done) {
  167. this.flags = ['-shell-restricted', '-halt-on-error']
  168. this.call(done)
  169. })
  170. it('should include the flags in the command', function () {
  171. const command = this.CommandRunner.run.args[0][1]
  172. const flags = command.filter(
  173. arg => arg === '-shell-restricted' || arg === '-halt-on-error'
  174. )
  175. flags.length.should.equal(2)
  176. flags[0].should.equal('-shell-restricted')
  177. flags[1].should.equal('-halt-on-error')
  178. })
  179. })
  180. describe('with the stopOnFirstError option', function () {
  181. beforeEach(function (done) {
  182. this.stopOnFirstError = true
  183. this.call(done)
  184. })
  185. it('should set the appropriate flags', function () {
  186. this.CommandRunner.run.should.have.been.calledWith(this.project_id, [
  187. 'latexmk',
  188. '-cd',
  189. '-jobname=output',
  190. '-auxdir=$COMPILE_DIR',
  191. '-outdir=$COMPILE_DIR',
  192. '-synctex=1',
  193. '-interaction=batchmode',
  194. '-halt-on-error',
  195. '-pdf',
  196. '$COMPILE_DIR/main-file.tex',
  197. ])
  198. })
  199. })
  200. })
  201. })