Просмотр исходного кода

Merge pull request #26637 from overleaf/bg-clsi-fix-process-group-for-local-compiles

fix "stop compile" option for local command runner in CE/SP

GitOrigin-RevId: 7986b505362aaf33ac6e161b3b54458baba1e2e6
Brian Gough 1 год назад
Родитель
Сommit
2f2862ecd7

+ 8 - 0
server-ce/test/helpers/compile.ts

@@ -9,6 +9,14 @@ export function throttledRecompile() {
   return recompile
 }
 
+export function stopCompile(options: { delay?: number } = {}) {
+  const { delay = 0 } = options
+  cy.wait(delay)
+  cy.log('Stop compile')
+  cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
+  cy.findByRole('menuitem', { name: 'Stop compilation' }).click()
+}
+
 export function prepareWaitForNextCompileSlot() {
   let lastCompile = 0
   function queueReset() {

+ 35 - 1
server-ce/test/sandboxed-compiles.spec.ts

@@ -1,7 +1,7 @@
 import { ensureUserExists, login } from './helpers/login'
 import { createProject } from './helpers/project'
 import { isExcludedBySharding, startWith } from './helpers/config'
-import { throttledRecompile } from './helpers/compile'
+import { throttledRecompile, stopCompile } from './helpers/compile'
 import { v4 as uuid } from 'uuid'
 import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished'
 import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
@@ -56,8 +56,40 @@ describe('SandboxedCompiles', function () {
     checkSyncTeX()
     checkXeTeX()
     checkRecompilesAfterErrors()
+    checkStopCompile()
   })
 
+  function checkStopCompile() {
+    it('users can stop a running compile', function () {
+      login('user@example.com')
+      createProject('test-project')
+      // create an infinite loop in the main document
+      // this will cause the compile to run indefinitely
+      cy.findByText('\\maketitle').parent().click()
+      cy.findByText('\\maketitle')
+        .parent()
+        .type('\n\\def\\x{{}Hello!\\par\\x}\\x')
+      cy.log('Start compile')
+      // We need to start the compile manually because we do not want to wait for it to finish
+      cy.findByText('Recompile').click()
+      // Now stop the compile and kill the latex process
+      stopCompile({ delay: 1000 })
+      cy.get('.logs-pane')
+        .invoke('text')
+        .should('match', /PDF Rendering Error|Compilation cancelled/)
+      // Check that the previous compile is not running in the background by
+      // disabling the infinite loop and recompiling
+      cy.findByText('\\def').parent().click()
+      cy.findByText('\\def').parent().type('{home}disabled loop% ')
+      cy.findByText('Recompile').click()
+      cy.get('.pdf-viewer').should('contain.text', 'disabled loop')
+      cy.get('.logs-pane').should(
+        'not.contain.text',
+        'A previous compile is still running'
+      )
+    })
+  }
+
   function checkSyncTeX() {
     // TODO(25342): re-enable
     // eslint-disable-next-line mocha/no-skipped-tests
@@ -227,6 +259,7 @@ describe('SandboxedCompiles', function () {
     checkSyncTeX()
     checkXeTeX()
     checkRecompilesAfterErrors()
+    checkStopCompile()
   })
 
   describe.skip('unavailable in CE', function () {
@@ -241,5 +274,6 @@ describe('SandboxedCompiles', function () {
     checkSyncTeX()
     checkXeTeX()
     checkRecompilesAfterErrors()
+    checkStopCompile()
   })
 })

+ 1 - 0
services/clsi/app/js/LocalCommandRunner.js

@@ -54,6 +54,7 @@ module.exports = CommandRunner = {
       cwd: directory,
       env,
       stdio: ['pipe', 'pipe', 'ignore'],
+      detached: true,
     })
 
     let stdout = ''

+ 47 - 0
services/clsi/test/acceptance/js/StopCompile.js

@@ -0,0 +1,47 @@
+const Client = require('./helpers/Client')
+const ClsiApp = require('./helpers/ClsiApp')
+const { expect } = require('chai')
+
+describe('Stop compile', function () {
+  before(function (done) {
+    this.request = {
+      options: {
+        timeout: 100,
+      }, // seconds
+      resources: [
+        {
+          path: 'main.tex',
+          content: `\
+\\documentclass{article}
+\\begin{document}
+\\def\\x{Hello!\\par\\x}
+\\x
+\\end{document}\
+`,
+        },
+      ],
+    }
+    this.project_id = Client.randomId()
+    ClsiApp.ensureRunning(() => {
+      // start the compile in the background
+      Client.compile(this.project_id, this.request, (error, res, body) => {
+        this.compileResult = { error, res, body }
+      })
+      // wait for 1 second before stopping the compile
+      setTimeout(() => {
+        Client.stopCompile(this.project_id, (error, res, body) => {
+          this.stopResult = { error, res, body }
+          setTimeout(done, 1000) // allow time for the compile request to terminate
+        })
+      }, 1000)
+    })
+  })
+
+  it('should force a compile response with an error status', function () {
+    expect(this.stopResult.error).to.be.null
+    expect(this.stopResult.res.statusCode).to.equal(204)
+    expect(this.compileResult.res.statusCode).to.equal(200)
+    expect(this.compileResult.body.compile.status).to.equal('terminated')
+    expect(this.compileResult.body.compile.error).to.equal('terminated')
+  })
+})

+ 10 - 0
services/clsi/test/acceptance/js/helpers/Client.js

@@ -42,6 +42,16 @@ module.exports = Client = {
     )
   },
 
+  stopCompile(projectId, callback) {
+    if (callback == null) {
+      callback = function () {}
+    }
+    return request.post(
+      { url: `${this.host}/project/${projectId}/compile/stop` },
+      callback
+    )
+  },
+
   clearCache(projectId, callback) {
     if (callback == null) {
       callback = function () {}