import Client from './helpers/Client.js' import { fetchNothing, fetchString } from '@overleaf/fetch-utils' import ClsiApp from './helpers/ClsiApp.js' import { expect } from 'chai' import Settings from '@overleaf/settings' import Metrics from '@overleaf/metrics' describe('Simple LaTeX file', function () { const content = `\ \\documentclass{article} \\begin{document} Hello world \\end{document}\ ` const scenarios = [ { description: 'simple file', request: { resources: [{ path: 'main.tex', content }], options: { compileGroup: 'simple-latex-file', }, }, }, { description: 'simple file (TL2017)', request: { resources: [{ path: 'main.tex', content }], options: { compileGroup: 'simple-latex-file', imageName: 'quay.io/sharelatex/texlive-full:2017.1', }, }, // In TL2017 latexmk performs an extra LaTeX pass even for this simple document. expectedRuns: 2, }, { description: 'clsi-perf request', request: { resources: [{ path: 'main.tex', content }], options: { enablePdfCaching: false, metricsPath: 'clsi-perf', metricsMethod: 'memoir-manual', compileGroup: 'clsi-perf', // only used by tests, not by the service }, }, }, ] for (const scenario of scenarios) { describe(scenario.description, function () { before(async function () { this.project_id = Client.randomId() this.request = scenario.request await ClsiApp.ensureRunning() try { this.body = await Client.compile(this.project_id, this.request) } catch (error) { this.error = error } }) after(function () { // Clear all metrics after each scenario if (Metrics.prom && Metrics.prom.register) { Metrics.prom.register.resetMetrics() } }) it('should return the PDF', function () { const pdf = Client.getOutputFile(this.body, 'pdf') pdf.type.should.equal('pdf') }) it('should return the log', function () { const log = Client.getOutputFile(this.body, 'log') log.type.should.equal('log') }) it('should provide the pdf for download', async function () { const pdf = Client.getOutputFile(this.body, 'pdf') const response = await fetchNothing(pdf.url) response.status.should.equal(200) }) it('should provide the log for download', async function () { const log = Client.getOutputFile(this.body, 'pdf') const response = await fetchNothing(log.url) response.status.should.equal(200) }) if (scenario.description === 'clsi-perf request') { it('should only emit e2e compile time metric', async function () { const metrics = await fetchString(`${Settings.apis.clsi.url}/metrics`) const byPath = `path="${scenario.request.options.metricsPath}"` const byMethod = `method="${scenario.request.options.metricsMethod}"` const byVariant = `variant="${scenario.request.options.metricsMethod}"` const byGroup = `group="${scenario.request.options.compileGroup}"` expect(metrics).to.not.include(byPath) expect(metrics).to.not.include(byMethod) expect(metrics).to.not.include(byGroup) expect(metrics).to.include(byVariant) expect(metrics.match(new RegExp(byVariant, 'g'))).to.have.lengthOf(1) }) } else { it('should shard metrics by compileGroup', async function () { const metrics = await fetchString(`${Settings.apis.clsi.url}/metrics`) const byGroup = `group="${scenario.request.options.compileGroup}"` expect(metrics).to.include(byGroup) expect(metrics.match(new RegExp(byGroup, 'g'))).to.have.lengthOf(134) }) } it('should return only the expected keys for stats and timings', function () { const { stats, timings } = this.body.compile let pdfCachingStats = [] let pdfCachingTimings = [] if (scenario.request.options.enablePdfCaching !== false) { pdfCachingStats = [ 'pdf-caching-total-ranges-size', 'pdf-caching-reclaimed-space', 'pdf-caching-new-ranges-size', 'pdf-caching-n-ranges', 'pdf-caching-n-new-ranges', ] pdfCachingTimings = [ 'compute-pdf-caching', 'pdf-caching-overhead-delete-stale-hashes', ] } const expectedRuns = scenario.expectedRuns ?? 1 // Note: chai's all.keys assertion rejects extra keys stats.should.have.all.keys( 'isInitialCompile', 'latexmk-errors', 'latex-runs', 'latex-runs-with-errors', `latex-runs-${expectedRuns}`, `latex-runs-with-errors-${expectedRuns}`, 'pdf-size', ...pdfCachingStats ) timings.should.have.all.keys( 'sync', 'compile', 'output', 'compileE2E', ...pdfCachingTimings ) }) it('should report the correct number of runs', function () { const { stats } = this.body.compile expect(stats['latex-runs']).to.equal(scenario.expectedRuns ?? 1) }) }) } describe('document with shell commands', function () { before(async function () { this.project_id = Client.randomId() this.request = { resources: [ { path: 'main.tex', content: `\ \\documentclass{article} \\begin{document} Testing system calls: \\immediate\\write18{/bin/date > date.txt} The current date from system is: \\input{date.txt} The current date from popen is: \\input{"|date"} \\end{document}\ `, }, ], } await ClsiApp.ensureRunning() }) it('should compile successfully', async function () { const body = await Client.compile(this.project_id, this.request) expect(body).to.exist expect(body.compile?.status, 'compile status').to.equal('success') }) it('should return the PDF', async function () { const body = await Client.compile(this.project_id, this.request) const pdf = Client.getOutputFile(body, 'pdf') expect(pdf, 'pdf file not produced').to.exist expect(pdf.type, 'invalid pdf file').to.equal('pdf') }) }) })