瀏覽代碼

Merge pull request #17935 from overleaf/ar-refactor-compile-async

[web] make CompileManager async

GitOrigin-RevId: 617bde1f429fa9aafc7d4bf4ec628b2a22386b19
Andrew Rumble 2 年之前
父節點
當前提交
9c3d9ef590
共有 2 個文件被更改,包括 339 次插入 和 396 次删除
  1. 180 215
      services/web/app/src/Features/Compile/CompileManager.js
  2. 159 181
      services/web/test/unit/src/Compile/CompileManagerTests.js

+ 180 - 215
services/web/app/src/Features/Compile/CompileManager.js

@@ -9,254 +9,219 @@ const ClsiManager = require('./ClsiManager')
 const Metrics = require('@overleaf/metrics')
 const { RateLimiter } = require('../../infrastructure/RateLimiter')
 const UserAnalyticsIdCache = require('../Analytics/UserAnalyticsIdCache')
+const {
+  callbackify,
+  callbackifyMultiResult,
+} = require('@overleaf/promise-utils')
 
-module.exports = CompileManager = {
-  compile(projectId, userId, options = {}, _callback) {
-    const timer = new Metrics.Timer('editor.compile')
-    const callback = function (...args) {
+function instrumentWithTimer(fn, key) {
+  return async (...args) => {
+    const timer = new Metrics.Timer(key)
+    try {
+      return await fn(...args)
+    } finally {
       timer.done()
-      _callback(...args)
     }
+  }
+}
 
-    CompileManager._checkIfRecentlyCompiled(
-      projectId,
-      userId,
-      function (error, recentlyCompiled) {
-        if (error) {
-          return callback(error)
-        }
-        if (recentlyCompiled) {
-          return callback(null, 'too-recently-compiled', [])
-        }
-
-        CompileManager._checkIfAutoCompileLimitHasBeenHit(
-          options.isAutoCompile,
-          'everyone',
-          function (err, canCompile) {
-            if (err || !canCompile) {
-              return callback(null, 'autocompile-backoff', [])
-            }
+async function compile(projectId, userId, options = {}) {
+  const recentlyCompiled = await CompileManager._checkIfRecentlyCompiled(
+    projectId,
+    userId
+  )
+  if (recentlyCompiled) {
+    return { status: 'too-recently-compiled', outputFiles: [] }
+  }
 
-            ProjectRootDocManager.ensureRootDocumentIsSet(
-              projectId,
-              function (error) {
-                if (error) {
-                  return callback(error)
-                }
-                CompileManager.getProjectCompileLimits(
-                  projectId,
-                  function (error, limits) {
-                    if (error) {
-                      return callback(error)
-                    }
-                    for (const key in limits) {
-                      const value = limits[key]
-                      options[key] = value
-                    }
-                    // Put a lower limit on autocompiles for free users, based on compileGroup
-                    CompileManager._checkCompileGroupAutoCompileLimit(
-                      options.isAutoCompile,
-                      limits.compileGroup,
-                      function (err, canCompile) {
-                        if (err || !canCompile) {
-                          return callback(null, 'autocompile-backoff', [])
-                        }
-                        // only pass userId down to clsi if this is a per-user compile
-                        const compileAsUser = Settings.disablePerUserCompiles
-                          ? undefined
-                          : userId
-                        ClsiManager.sendRequest(
-                          projectId,
-                          compileAsUser,
-                          options,
-                          function (
-                            error,
-                            status,
-                            outputFiles,
-                            clsiServerId,
-                            validationProblems,
-                            stats,
-                            timings,
-                            outputUrlPrefix
-                          ) {
-                            if (error) {
-                              return callback(error)
-                            }
-                            callback(
-                              null,
-                              status,
-                              outputFiles,
-                              clsiServerId,
-                              limits,
-                              validationProblems,
-                              stats,
-                              timings,
-                              outputUrlPrefix
-                            )
-                          }
-                        )
-                      }
-                    )
-                  }
-                )
-              }
-            )
-          }
-        )
-      }
+  try {
+    const canCompile = await CompileManager._checkIfAutoCompileLimitHasBeenHit(
+      options.isAutoCompile,
+      'everyone'
     )
-  },
+    if (!canCompile) {
+      return { status: 'autocompile-backoff', outputFiles: [] }
+    }
+  } catch (error) {
+    return { status: 'autocompile-backoff', outputFiles: [] }
+  }
 
-  stopCompile(projectId, userId, callback) {
-    CompileManager.getProjectCompileLimits(projectId, function (error, limits) {
-      if (error) {
-        return callback(error)
-      }
-      ClsiManager.stopCompile(projectId, userId, limits, callback)
-    })
-  },
+  await ProjectRootDocManager.promises.ensureRootDocumentIsSet(projectId)
 
-  deleteAuxFiles(projectId, userId, clsiserverid, callback) {
-    CompileManager.getProjectCompileLimits(projectId, function (error, limits) {
-      if (error) {
-        return callback(error)
-      }
-      ClsiManager.deleteAuxFiles(
-        projectId,
-        userId,
-        limits,
-        clsiserverid,
-        callback
-      )
-    })
-  },
+  const limits =
+    await CompileManager.promises.getProjectCompileLimits(projectId)
+  for (const key in limits) {
+    const value = limits[key]
+    options[key] = value
+  }
 
-  getProjectCompileLimits(projectId, callback) {
-    ProjectGetter.getProject(
-      projectId,
-      { owner_ref: 1 },
-      function (error, project) {
-        if (error) {
-          return callback(error)
-        }
-        UserGetter.getUser(
-          project.owner_ref,
-          {
-            _id: 1,
-            alphaProgram: 1,
-            analyticsId: 1,
-            betaProgram: 1,
-            features: 1,
-          },
-          function (err, owner) {
-            if (err) {
-              return callback(err)
-            }
-            const ownerFeatures = (owner && owner.features) || {}
-            // put alpha users into their own compile group
-            if (owner && owner.alphaProgram) {
-              ownerFeatures.compileGroup = 'alpha'
-            }
-            UserAnalyticsIdCache.callbacks.get(
-              owner._id,
-              function (err, analyticsId) {
-                if (err) {
-                  return callback(err)
-                }
-                const compileGroup =
-                  ownerFeatures.compileGroup ||
-                  Settings.defaultFeatures.compileGroup
-                const limits = {
-                  timeout:
-                    ownerFeatures.compileTimeout ||
-                    Settings.defaultFeatures.compileTimeout,
-                  compileGroup,
-                  compileBackendClass:
-                    compileGroup === 'standard' ? 'n2d' : 'c2d',
-                  ownerAnalyticsId: analyticsId,
-                }
-                callback(null, limits)
-              }
-            )
-          }
-        )
-      }
+  try {
+    const canCompile = await CompileManager._checkCompileGroupAutoCompileLimit(
+      options.isAutoCompile,
+      limits.compileGroup
     )
+    if (!canCompile) {
+      return { status: 'autocompile-backoff', outputFiles: [] }
+    }
+  } catch (error) {
+    return { message: 'autocompile-backoff', outputFiles: [] }
+  }
+
+  // only pass userId down to clsi if this is a per-user compile
+  const compileAsUser = Settings.disablePerUserCompiles ? undefined : userId
+  const {
+    status,
+    outputFiles,
+    clsiServerId,
+    validationProblems,
+    stats,
+    timings,
+    outputUrlPrefix,
+  } = await ClsiManager.promises.sendRequest(projectId, compileAsUser, options)
+
+  return {
+    status,
+    outputFiles,
+    clsiServerId,
+    limits,
+    validationProblems,
+    stats,
+    timings,
+    outputUrlPrefix,
+  }
+}
+
+const instrumentedCompile = instrumentWithTimer(compile, 'editor.compile')
+
+async function getProjectCompileLimits(projectId) {
+  const project = await ProjectGetter.promises.getProject(projectId, {
+    owner_ref: 1,
+  })
+
+  const owner = await UserGetter.promises.getUser(project.owner_ref, {
+    _id: 1,
+    alphaProgram: 1,
+    analyticsId: 1,
+    betaProgram: 1,
+    features: 1,
+  })
+
+  const ownerFeatures = (owner && owner.features) || {}
+  // put alpha users into their own compile group
+  if (owner && owner.alphaProgram) {
+    ownerFeatures.compileGroup = 'alpha'
+  }
+  const analyticsId = await UserAnalyticsIdCache.get(owner._id)
+
+  const compileGroup =
+    ownerFeatures.compileGroup || Settings.defaultFeatures.compileGroup
+  const limits = {
+    timeout:
+      ownerFeatures.compileTimeout || Settings.defaultFeatures.compileTimeout,
+    compileGroup,
+    compileBackendClass: compileGroup === 'standard' ? 'n2d' : 'c2d',
+    ownerAnalyticsId: analyticsId,
+  }
+  return limits
+}
+
+async function wordCount(projectId, userId, file, clsiserverid) {
+  const limits =
+    await CompileManager.promises.getProjectCompileLimits(projectId)
+  return await ClsiManager.promises.wordCount(
+    projectId,
+    userId,
+    file,
+    limits,
+    clsiserverid
+  )
+}
+
+async function stopCompile(projectId, userId) {
+  const limits =
+    await CompileManager.promises.getProjectCompileLimits(projectId)
+
+  return await ClsiManager.promises.stopCompile(projectId, userId, limits)
+}
+
+async function deleteAuxFiles(projectId, userId, clsiserverid) {
+  const limits =
+    await CompileManager.promises.getProjectCompileLimits(projectId)
+
+  return await ClsiManager.promises.deleteAuxFiles(
+    projectId,
+    userId,
+    limits,
+    clsiserverid
+  )
+}
+
+module.exports = CompileManager = {
+  promises: {
+    compile: instrumentedCompile,
+    deleteAuxFiles,
+    getProjectCompileLimits,
+    stopCompile,
+    wordCount,
   },
+  compile: callbackifyMultiResult(instrumentedCompile, [
+    'status',
+    'outputFiles',
+    'clsiServerId',
+    'limits',
+    'validationProblems',
+    'stats',
+    'timings',
+    'outputUrlPrefix',
+  ]),
+
+  stopCompile: callbackify(stopCompile),
+
+  deleteAuxFiles: callbackify(deleteAuxFiles),
+
+  getProjectCompileLimits: callbackify(getProjectCompileLimits),
 
   COMPILE_DELAY: 1, // seconds
-  _checkIfRecentlyCompiled(projectId, userId, callback) {
+  async _checkIfRecentlyCompiled(projectId, userId) {
     const key = `compile:${projectId}:${userId}`
-    rclient.set(
-      key,
-      true,
-      'EX',
-      this.COMPILE_DELAY,
-      'NX',
-      function (error, ok) {
-        if (error) {
-          return callback(error)
-        }
-        if (ok === 'OK') {
-          callback(null, false)
-        } else {
-          callback(null, true)
-        }
-      }
-    )
+    const ok = await rclient.set(key, true, 'EX', this.COMPILE_DELAY, 'NX')
+    return ok !== 'OK'
   },
 
-  _checkCompileGroupAutoCompileLimit(isAutoCompile, compileGroup, callback) {
+  async _checkCompileGroupAutoCompileLimit(isAutoCompile, compileGroup) {
     if (!isAutoCompile) {
-      return callback(null, true)
+      return true
     }
     if (compileGroup === 'standard') {
       // apply extra limits to the standard compile group
-      CompileManager._checkIfAutoCompileLimitHasBeenHit(
+      return await CompileManager._checkIfAutoCompileLimitHasBeenHit(
         isAutoCompile,
-        compileGroup,
-        callback
+        compileGroup
       )
     } else {
       Metrics.inc(`auto-compile-${compileGroup}`)
-      callback(null, true)
+      return true
     }
   }, // always allow priority group users to compile
 
-  _checkIfAutoCompileLimitHasBeenHit(isAutoCompile, compileGroup, callback) {
+  async _checkIfAutoCompileLimitHasBeenHit(isAutoCompile, compileGroup) {
     if (!isAutoCompile) {
-      return callback(null, true)
+      return true
     }
     Metrics.inc(`auto-compile-${compileGroup}`)
     const rateLimiter = getAutoCompileRateLimiter(compileGroup)
-    rateLimiter
-      .consume('global', 1, { method: 'global' })
-      .then(() => {
-        callback(null, true)
-      })
-      .catch(() => {
-        // Don't differentiate between errors and rate limits. Silently trigger
-        // the rate limit if there's an error consuming the points.
-        Metrics.inc(`auto-compile-${compileGroup}-limited`)
-        callback(null, false)
-      })
+    try {
+      await rateLimiter.consume('global', 1, { method: 'global' })
+      return true
+    } catch (e) {
+      // Don't differentiate between errors and rate limits. Silently trigger
+      // the rate limit if there's an error consuming the points.
+      Metrics.inc(`auto-compile-${compileGroup}-limited`)
+      return false
+    }
   },
 
-  wordCount(projectId, userId, file, clsiserverid, callback) {
-    CompileManager.getProjectCompileLimits(projectId, function (error, limits) {
-      if (error) {
-        return callback(error)
-      }
-      ClsiManager.wordCount(
-        projectId,
-        userId,
-        file,
-        limits,
-        clsiserverid,
-        callback
-      )
-    })
-  },
+  wordCount: callbackify(wordCount),
 }
 
 const autoCompileRateLimiters = new Map()

+ 159 - 181
services/web/test/unit/src/Compile/CompileManagerTests.js

@@ -29,18 +29,21 @@ describe('CompileManager', function () {
           rateLimit: { autoCompile: {} },
         }),
         '../../infrastructure/RedisWrapper': {
-          client: () => (this.rclient = { auth() {} }),
+          client: () =>
+            (this.rclient = {
+              auth() {},
+            }),
         },
-        '../Project/ProjectRootDocManager': (this.ProjectRootDocManager = {}),
-        '../Project/ProjectGetter': (this.ProjectGetter = {}),
-        '../User/UserGetter': (this.UserGetter = {}),
-        './ClsiManager': (this.ClsiManager = {}),
+        '../Project/ProjectRootDocManager': (this.ProjectRootDocManager = {
+          promises: {},
+        }),
+        '../Project/ProjectGetter': (this.ProjectGetter = { promises: {} }),
+        '../User/UserGetter': (this.UserGetter = { promises: {} }),
+        './ClsiManager': (this.ClsiManager = { promises: {} }),
         '../../infrastructure/RateLimiter': this.RateLimiter,
         '@overleaf/metrics': this.Metrics,
         '../Analytics/UserAnalyticsIdCache': (this.UserAnalyticsIdCache = {
-          callbacks: {
-            get: sinon.stub().yields(null, 'abc'),
-          },
+          get: sinon.stub().resolves('abc'),
         }),
       },
     })
@@ -57,36 +60,42 @@ describe('CompileManager', function () {
     beforeEach(function () {
       this.CompileManager._checkIfRecentlyCompiled = sinon
         .stub()
-        .callsArgWith(2, null, false)
-      this.ProjectRootDocManager.ensureRootDocumentIsSet = sinon
+        .resolves(false)
+      this.ProjectRootDocManager.promises.ensureRootDocumentIsSet = sinon
         .stub()
-        .callsArgWith(1, null)
-      this.CompileManager.getProjectCompileLimits = sinon
+        .resolves()
+      this.CompileManager.promises.getProjectCompileLimits = sinon
         .stub()
-        .callsArgWith(1, null, this.limits)
-      this.ClsiManager.sendRequest = sinon
-        .stub()
-        .callsArgWith(
-          3,
-          null,
-          (this.status = 'mock-status'),
-          (this.outputFiles = 'mock output files'),
-          (this.output = 'mock output')
-        )
+        .resolves(this.limits)
+      this.ClsiManager.promises.sendRequest = sinon.stub().resolves({
+        status: (this.status = 'mock-status'),
+        outputFiles: (this.outputFiles = []),
+        clsiServerId: (this.output = 'mock output'),
+      })
     })
 
     describe('succesfully', function () {
-      beforeEach(function () {
-        this.CompileManager._checkIfAutoCompileLimitHasBeenHit = (
+      let result
+      beforeEach(async function () {
+        this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
           isAutoCompile,
-          compileGroup,
-          cb
-        ) => cb(null, true)
-        this.CompileManager.compile(
+          compileGroup
+        ) => true
+        this.ProjectGetter.promises.getProject = sinon
+          .stub()
+          .resolves(
+            (this.project = { owner_ref: (this.owner_id = 'owner-id-123') })
+          )
+        this.UserGetter.promises.getUser = sinon.stub().resolves(
+          (this.user = {
+            features: { compileTimeout: '20s', compileGroup: 'standard' },
+            analyticsId: 'abc',
+          })
+        )
+        result = await this.CompileManager.promises.compile(
           this.project_id,
           this.user_id,
-          {},
-          this.callback
+          {}
         )
       })
 
@@ -97,19 +106,19 @@ describe('CompileManager', function () {
       })
 
       it('should ensure that the root document is set', function () {
-        this.ProjectRootDocManager.ensureRootDocumentIsSet
+        this.ProjectRootDocManager.promises.ensureRootDocumentIsSet
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should get the project compile limits', function () {
-        this.CompileManager.getProjectCompileLimits
+        this.CompileManager.promises.getProjectCompileLimits
           .calledWith(this.project_id)
           .should.equal(true)
       })
 
       it('should run the compile with the compile limits', function () {
-        this.ClsiManager.sendRequest
+        this.ClsiManager.promises.sendRequest
           .calledWith(this.project_id, this.user_id, {
             timeout: this.limits.timeout,
             compileGroup: 'standard',
@@ -117,10 +126,10 @@ describe('CompileManager', function () {
           .should.equal(true)
       })
 
-      it('should call the callback with the output', function () {
-        this.callback
-          .calledWith(null, this.status, this.outputFiles, this.output)
-          .should.equal(true)
+      it('should resolve with the output', function () {
+        expect(result).to.haveOwnProperty('status', this.status)
+        expect(result).to.haveOwnProperty('clsiServerId', this.output)
+        expect(result).to.haveOwnProperty('outputFiles', this.outputFiles)
       })
 
       it('should time the compile', function () {
@@ -130,26 +139,24 @@ describe('CompileManager', function () {
 
     describe('when the project has been recently compiled', function () {
       it('should return', function (done) {
-        this.CompileManager._checkIfAutoCompileLimitHasBeenHit = (
+        this.CompileManager._checkIfAutoCompileLimitHasBeenHit = async (
           isAutoCompile,
-          compileGroup,
-          cb
-        ) => cb(null, true)
+          compileGroup
+        ) => true
         this.CompileManager._checkIfRecentlyCompiled = sinon
           .stub()
-          .callsArgWith(2, null, true)
-        this.CompileManager.compile(
-          this.project_id,
-          this.user_id,
-          {},
-          (err, status) => {
-            if (err) {
-              return done(err)
-            }
+          .resolves(true)
+        this.CompileManager.promises
+          .compile(this.project_id, this.user_id, {})
+          .then(({ status }) => {
             status.should.equal('too-recently-compiled')
             done()
-          }
-        )
+          })
+          .catch(error => {
+            // Catch any errors and fail the test
+            true.should.equal(false)
+            done(error)
+          })
       })
     })
 
@@ -157,60 +164,51 @@ describe('CompileManager', function () {
       it('should return', function (done) {
         this.CompileManager._checkIfAutoCompileLimitHasBeenHit = sinon
           .stub()
-          .callsArgWith(2, null, false)
-        this.CompileManager.compile(
-          this.project_id,
-          this.user_id,
-          {},
-          (err, status) => {
-            if (err) {
-              return done(err)
-            }
-            status.should.equal('autocompile-backoff')
+          .resolves(false)
+        this.CompileManager.promises
+          .compile(this.project_id, this.user_id, {})
+          .then(({ status }) => {
+            expect(status).to.equal('autocompile-backoff')
             done()
-          }
-        )
+          })
+          .catch(err => done(err))
       })
     })
   })
 
   describe('getProjectCompileLimits', function () {
-    beforeEach(function (done) {
+    beforeEach(async function () {
       this.features = {
         compileTimeout: (this.timeout = 42),
         compileGroup: (this.group = 'priority'),
       }
-      this.ProjectGetter.getProject = sinon
+      this.ProjectGetter.promises.getProject = sinon
         .stub()
-        .callsArgWith(
-          2,
-          null,
+        .resolves(
           (this.project = { owner_ref: (this.owner_id = 'owner-id-123') })
         )
-      this.UserGetter.getUser = sinon
+      this.UserGetter.promises.getUser = sinon
         .stub()
-        .callsArgWith(
-          2,
-          null,
-          (this.user = { features: this.features, analyticsId: 'abc' })
-        )
-      this.CompileManager.getProjectCompileLimits(
-        this.project_id,
-        (err, res) => {
-          this.callback(err, res)
-          done()
-        }
-      )
+        .resolves((this.user = { features: this.features, analyticsId: 'abc' }))
+      try {
+        const result =
+          await this.CompileManager.promises.getProjectCompileLimits(
+            this.project_id
+          )
+        this.callback(null, result)
+      } catch (error) {
+        this.callback(error)
+      }
     })
 
     it('should look up the owner of the project', function () {
-      this.ProjectGetter.getProject
+      this.ProjectGetter.promises.getProject
         .calledWith(this.project_id, { owner_ref: 1 })
         .should.equal(true)
     })
 
     it("should look up the owner's features", function () {
-      this.UserGetter.getUser
+      this.UserGetter.promises.getUser
         .calledWith(this.project.owner_ref, {
           _id: 1,
           alphaProgram: 1,
@@ -239,12 +237,12 @@ describe('CompileManager', function () {
         compileTimeout: 42,
         compileGroup: 'standard',
       }
-      this.ProjectGetter.getProject = sinon
+      this.ProjectGetter.promises.getProject = sinon
         .stub()
-        .yields(null, { owner_ref: 'owner-id-123' })
-      this.UserGetter.getUser = sinon
+        .resolves({ owner_ref: 'owner-id-123' })
+      this.UserGetter.promises.getUser = sinon
         .stub()
-        .yields(null, { features: this.features, analyticsId: 'abc' })
+        .resolves({ features: this.features, analyticsId: 'abc' })
     })
 
     describe('with priority compile', function () {
@@ -265,47 +263,45 @@ describe('CompileManager', function () {
   })
 
   describe('deleteAuxFiles', function () {
-    beforeEach(function () {
-      this.CompileManager.getProjectCompileLimits = sinon
+    let result
+
+    beforeEach(async function () {
+      this.CompileManager.promises.getProjectCompileLimits = sinon
         .stub()
-        .callsArgWith(
-          1,
-          null,
-          (this.limits = { compileGroup: 'mock-compile-group' })
-        )
-      this.ClsiManager.deleteAuxFiles = sinon.stub().callsArg(3)
-      this.CompileManager.deleteAuxFiles(
+        .resolves((this.limits = { compileGroup: 'mock-compile-group' }))
+      this.ClsiManager.promises.deleteAuxFiles = sinon.stub().resolves('test')
+      result = await this.CompileManager.promises.deleteAuxFiles(
         this.project_id,
-        this.user_id,
-        this.callback
+        this.user_id
       )
     })
 
     it('should look up the compile group to use', function () {
-      this.CompileManager.getProjectCompileLimits
+      this.CompileManager.promises.getProjectCompileLimits
         .calledWith(this.project_id)
         .should.equal(true)
     })
 
     it('should delete the aux files', function () {
-      this.ClsiManager.deleteAuxFiles
+      this.ClsiManager.promises.deleteAuxFiles
         .calledWith(this.project_id, this.user_id, this.limits)
         .should.equal(true)
     })
 
-    it('should call the callback', function () {
-      this.callback.called.should.equal(true)
+    it('should resolve', function () {
+      expect(result).not.to.be.undefined
     })
   })
 
   describe('_checkIfRecentlyCompiled', function () {
     describe('when the key exists in redis', function () {
-      beforeEach(function () {
-        this.rclient.set = sinon.stub().callsArgWith(5, null, null)
-        this.CompileManager._checkIfRecentlyCompiled(
+      let result
+
+      beforeEach(async function () {
+        this.rclient.set = sinon.stub().resolves(null)
+        result = await this.CompileManager._checkIfRecentlyCompiled(
           this.project_id,
-          this.user_id,
-          this.callback
+          this.user_id
         )
       })
 
@@ -321,18 +317,19 @@ describe('CompileManager', function () {
           .should.equal(true)
       })
 
-      it('should call the callback with true', function () {
-        this.callback.calledWith(null, true).should.equal(true)
+      it('should resolve with true', function () {
+        result.should.equal(true)
       })
     })
 
     describe('when the key does not exist in redis', function () {
-      beforeEach(function () {
-        this.rclient.set = sinon.stub().callsArgWith(5, null, 'OK')
-        this.CompileManager._checkIfRecentlyCompiled(
+      let result
+
+      beforeEach(async function () {
+        this.rclient.set = sinon.stub().resolves('OK')
+        result = await this.CompileManager._checkIfRecentlyCompiled(
           this.project_id,
-          this.user_id,
-          this.callback
+          this.user_id
         )
       })
 
@@ -348,105 +345,86 @@ describe('CompileManager', function () {
           .should.equal(true)
       })
 
-      it('should call the callback with false', function () {
-        this.callback.calledWith(null, false).should.equal(true)
+      it('should resolve with false', function () {
+        result.should.equal(false)
       })
     })
   })
 
   describe('_checkIfAutoCompileLimitHasBeenHit', function () {
-    it('should be able to compile if it is not an autocompile', function (done) {
-      this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
-        false,
-        'everyone',
-        (err, canCompile) => {
-          if (err) {
-            return done(err)
-          }
-          canCompile.should.equal(true)
-          done()
-        }
-      )
+    it('should be able to compile if it is not an autocompile', async function () {
+      const canCompile =
+        await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
+          false,
+          'everyone'
+        )
+      expect(canCompile).to.equal(true)
     })
 
-    it('should be able to compile if rate limit has remaining', function (done) {
-      this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
-        true,
-        'everyone',
-        (err, canCompile) => {
-          if (err) {
-            return done(err)
-          }
-          expect(this.rateLimiter.consume).to.have.been.calledWith('global')
-          canCompile.should.equal(true)
-          done()
-        }
-      )
+    it('should be able to compile if rate limit has remaining', async function () {
+      const canCompile =
+        await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
+          true,
+          'everyone'
+        )
+
+      expect(this.rateLimiter.consume).to.have.been.calledWith('global')
+      expect(canCompile).to.equal(true)
     })
 
-    it('should be not able to compile if rate limit has no remianing', function (done) {
+    it('should be not able to compile if rate limit has no remianing', async function () {
       this.rateLimiter.consume.rejects({ remainingPoints: 0 })
-      this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
-        true,
-        'everyone',
-        (err, canCompile) => {
-          if (err) {
-            return done(err)
-          }
-          canCompile.should.equal(false)
-          done()
-        }
-      )
+      const canCompile =
+        await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
+          true,
+          'everyone'
+        )
+
+      expect(canCompile).to.equal(false)
     })
 
-    it('should return false if there is an error in the rate limit', function (done) {
+    it('should return false if there is an error in the rate limit', async function () {
       this.rateLimiter.consume.rejects(new Error('BOOM!'))
-      this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
-        true,
-        'everyone',
-        (err, canCompile) => {
-          if (err) {
-            return done(err)
-          }
-          canCompile.should.equal(false)
-          done()
-        }
-      )
+      const canCompile =
+        await this.CompileManager._checkIfAutoCompileLimitHasBeenHit(
+          true,
+          'everyone'
+        )
+
+      expect(canCompile).to.equal(false)
     })
   })
 
   describe('wordCount', function () {
-    beforeEach(function () {
-      this.CompileManager.getProjectCompileLimits = sinon
+    let result
+    const wordCount = 1
+
+    beforeEach(async function () {
+      this.CompileManager.promises.getProjectCompileLimits = sinon
         .stub()
-        .callsArgWith(
-          1,
-          null,
-          (this.limits = { compileGroup: 'mock-compile-group' })
-        )
-      this.ClsiManager.wordCount = sinon.stub().callsArg(4)
-      this.CompileManager.wordCount(
+        .resolves((this.limits = { compileGroup: 'mock-compile-group' }))
+      this.ClsiManager.promises.wordCount = sinon.stub().resolves(wordCount)
+      result = await this.CompileManager.promises.wordCount(
         this.project_id,
         this.user_id,
-        false,
-        this.callback
+        false
       )
     })
 
     it('should look up the compile group to use', function () {
-      this.CompileManager.getProjectCompileLimits
+      this.CompileManager.promises.getProjectCompileLimits
         .calledWith(this.project_id)
         .should.equal(true)
     })
 
     it('should call wordCount for project', function () {
-      this.ClsiManager.wordCount
+      this.ClsiManager.promises.wordCount
         .calledWith(this.project_id, this.user_id, false, this.limits)
         .should.equal(true)
     })
 
-    it('should call the callback', function () {
-      this.callback.called.should.equal(true)
+    it('should resolve with the wordCount from the ClsiManager', function () {
+      expect(result).to.equal(wordCount)
     })
   })
 })