SimpleLatexFileTests.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import Client from './helpers/Client.js'
  2. import { fetchNothing, fetchString } from '@overleaf/fetch-utils'
  3. import ClsiApp from './helpers/ClsiApp.js'
  4. import { expect } from 'chai'
  5. import Settings from '@overleaf/settings'
  6. import Metrics from '@overleaf/metrics'
  7. describe('Simple LaTeX file', function () {
  8. const content = `\
  9. \\documentclass{article}
  10. \\begin{document}
  11. Hello world
  12. \\end{document}\
  13. `
  14. const scenarios = [
  15. {
  16. description: 'simple file',
  17. request: {
  18. resources: [{ path: 'main.tex', content }],
  19. options: {
  20. compileGroup: 'simple-latex-file',
  21. },
  22. },
  23. },
  24. {
  25. description: 'simple file (TL2017)',
  26. request: {
  27. resources: [{ path: 'main.tex', content }],
  28. options: {
  29. compileGroup: 'simple-latex-file',
  30. imageName: 'quay.io/sharelatex/texlive-full:2017.1',
  31. },
  32. },
  33. // In TL2017 latexmk performs an extra LaTeX pass even for this simple document.
  34. expectedRuns: 2,
  35. },
  36. {
  37. description: 'clsi-perf request',
  38. request: {
  39. resources: [{ path: 'main.tex', content }],
  40. options: {
  41. enablePdfCaching: false,
  42. metricsPath: 'clsi-perf',
  43. metricsMethod: 'memoir-manual',
  44. compileGroup: 'clsi-perf', // only used by tests, not by the service
  45. },
  46. },
  47. },
  48. ]
  49. for (const scenario of scenarios) {
  50. describe(scenario.description, function () {
  51. before(async function () {
  52. this.project_id = Client.randomId()
  53. this.request = scenario.request
  54. await ClsiApp.ensureRunning()
  55. try {
  56. this.body = await Client.compile(this.project_id, this.request)
  57. } catch (error) {
  58. this.error = error
  59. }
  60. })
  61. after(function () {
  62. // Clear all metrics after each scenario
  63. if (Metrics.prom && Metrics.prom.register) {
  64. Metrics.prom.register.resetMetrics()
  65. }
  66. })
  67. it('should return the PDF', function () {
  68. const pdf = Client.getOutputFile(this.body, 'pdf')
  69. pdf.type.should.equal('pdf')
  70. })
  71. it('should return the log', function () {
  72. const log = Client.getOutputFile(this.body, 'log')
  73. log.type.should.equal('log')
  74. })
  75. it('should provide the pdf for download', async function () {
  76. const pdf = Client.getOutputFile(this.body, 'pdf')
  77. const response = await fetchNothing(pdf.url)
  78. response.status.should.equal(200)
  79. })
  80. it('should provide the log for download', async function () {
  81. const log = Client.getOutputFile(this.body, 'pdf')
  82. const response = await fetchNothing(log.url)
  83. response.status.should.equal(200)
  84. })
  85. if (scenario.description === 'clsi-perf request') {
  86. it('should only emit e2e compile time metric', async function () {
  87. const metrics = await fetchString(`${Settings.apis.clsi.url}/metrics`)
  88. const byPath = `path="${scenario.request.options.metricsPath}"`
  89. const byMethod = `method="${scenario.request.options.metricsMethod}"`
  90. const byVariant = `variant="${scenario.request.options.metricsMethod}"`
  91. const byGroup = `group="${scenario.request.options.compileGroup}"`
  92. expect(metrics).to.not.include(byPath)
  93. expect(metrics).to.not.include(byMethod)
  94. expect(metrics).to.not.include(byGroup)
  95. expect(metrics).to.include(byVariant)
  96. expect(metrics.match(new RegExp(byVariant, 'g'))).to.have.lengthOf(1)
  97. })
  98. } else {
  99. it('should shard metrics by compileGroup', async function () {
  100. const metrics = await fetchString(`${Settings.apis.clsi.url}/metrics`)
  101. const byGroup = `group="${scenario.request.options.compileGroup}"`
  102. expect(metrics).to.include(byGroup)
  103. expect(metrics.match(new RegExp(byGroup, 'g'))).to.have.lengthOf(134)
  104. })
  105. }
  106. it('should return only the expected keys for stats and timings', function () {
  107. const { stats, timings } = this.body.compile
  108. let pdfCachingStats = []
  109. let pdfCachingTimings = []
  110. if (scenario.request.options.enablePdfCaching !== false) {
  111. pdfCachingStats = [
  112. 'pdf-caching-total-ranges-size',
  113. 'pdf-caching-reclaimed-space',
  114. 'pdf-caching-new-ranges-size',
  115. 'pdf-caching-n-ranges',
  116. 'pdf-caching-n-new-ranges',
  117. ]
  118. pdfCachingTimings = [
  119. 'compute-pdf-caching',
  120. 'pdf-caching-overhead-delete-stale-hashes',
  121. ]
  122. }
  123. const expectedRuns = scenario.expectedRuns ?? 1
  124. // Note: chai's all.keys assertion rejects extra keys
  125. stats.should.have.all.keys(
  126. 'isInitialCompile',
  127. 'latexmk-errors',
  128. 'latex-runs',
  129. 'latex-runs-with-errors',
  130. `latex-runs-${expectedRuns}`,
  131. `latex-runs-with-errors-${expectedRuns}`,
  132. 'pdf-size',
  133. ...pdfCachingStats
  134. )
  135. timings.should.have.all.keys(
  136. 'sync',
  137. 'compile',
  138. 'output',
  139. 'compileE2E',
  140. ...pdfCachingTimings
  141. )
  142. })
  143. it('should report the correct number of runs', function () {
  144. const { stats } = this.body.compile
  145. expect(stats['latex-runs']).to.equal(scenario.expectedRuns ?? 1)
  146. })
  147. })
  148. }
  149. describe('document with shell commands', function () {
  150. before(async function () {
  151. this.project_id = Client.randomId()
  152. this.request = {
  153. resources: [
  154. {
  155. path: 'main.tex',
  156. content: `\
  157. \\documentclass{article}
  158. \\begin{document}
  159. Testing system calls:
  160. \\immediate\\write18{/bin/date > date.txt}
  161. The current date from system is: \\input{date.txt}
  162. The current date from popen is: \\input{"|date"}
  163. \\end{document}\
  164. `,
  165. },
  166. ],
  167. }
  168. await ClsiApp.ensureRunning()
  169. })
  170. it('should compile successfully', async function () {
  171. const body = await Client.compile(this.project_id, this.request)
  172. expect(body).to.exist
  173. expect(body.compile?.status, 'compile status').to.equal('success')
  174. })
  175. it('should return the PDF', async function () {
  176. const body = await Client.compile(this.project_id, this.request)
  177. const pdf = Client.getOutputFile(body, 'pdf')
  178. expect(pdf, 'pdf file not produced').to.exist
  179. expect(pdf.type, 'invalid pdf file').to.equal('pdf')
  180. })
  181. })
  182. })