mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 09:09:36 +02:00
Merge pull request #32710 from overleaf/mg-project-history-metrics
Add diagnostic annotations to LazyStringFileData toEager errors GitOrigin-RevId: 47575586bb869d65e4eb443cc9f1215b6f245255
This commit is contained in:
@@ -4,11 +4,13 @@
|
||||
const _ = require('lodash')
|
||||
const assert = require('check-types').assert
|
||||
|
||||
const OError = require('@overleaf/o-error')
|
||||
const Blob = require('../blob')
|
||||
const FileData = require('./')
|
||||
const EagerStringFileData = require('./string_file_data')
|
||||
const EditOperation = require('../operation/edit_operation')
|
||||
const EditOperationBuilder = require('../operation/edit_operation_builder')
|
||||
const TextOperation = require('../operation/text_operation')
|
||||
|
||||
/**
|
||||
* @import { BlobStore, ReadonlyBlobStore, RangesBlob, RawHashFileData, RawLazyStringFileData } from '../types'
|
||||
@@ -152,7 +154,25 @@ class LazyStringFileData extends FileData {
|
||||
ranges?.comments,
|
||||
ranges?.trackedChanges
|
||||
)
|
||||
applyOperations(this.operations, file)
|
||||
try {
|
||||
applyOperations(this.operations, file)
|
||||
} catch (err) {
|
||||
const firstOp = this.operations[0]
|
||||
const firstOpBaseLength =
|
||||
firstOp instanceof TextOperation ? firstOp.baseLength : undefined
|
||||
throw OError.tag(err, 'failed to apply operations in toEager', {
|
||||
blobHash: this.hash,
|
||||
blobContentLength: content.length,
|
||||
metadataStringLength: this.stringLength,
|
||||
totalOperations: this.operations.length,
|
||||
firstOpBaseLength,
|
||||
contentMatchesMetadata: content.length === this.stringLength,
|
||||
contentMatchesFirstOp:
|
||||
typeof firstOpBaseLength === 'number'
|
||||
? content.length === firstOpBaseLength
|
||||
: undefined,
|
||||
})
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
@@ -172,7 +192,18 @@ class LazyStringFileData extends FileData {
|
||||
* @param {EditOperation} operation
|
||||
*/
|
||||
edit(operation) {
|
||||
this.stringLength = operation.applyToLength(this.stringLength)
|
||||
try {
|
||||
this.stringLength = operation.applyToLength(this.stringLength)
|
||||
} catch (err) {
|
||||
const baseLength =
|
||||
operation instanceof TextOperation ? operation.baseLength : undefined
|
||||
throw OError.tag(err, 'failed to apply operation length in edit', {
|
||||
blobHash: this.hash,
|
||||
metadataStringLength: this.stringLength,
|
||||
operationBaseLength: baseLength,
|
||||
totalExistingOperations: this.operations.length,
|
||||
})
|
||||
}
|
||||
this.operations.push(operation)
|
||||
}
|
||||
|
||||
@@ -205,7 +236,17 @@ class LazyStringFileData extends FileData {
|
||||
* @returns {void}
|
||||
*/
|
||||
function applyOperations(operations, file) {
|
||||
_.each(operations, operation => operation.apply(file))
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
try {
|
||||
operations[i].apply(file)
|
||||
} catch (err) {
|
||||
throw OError.tag(err, 'operation failed during applyOperations', {
|
||||
operationIndex: i,
|
||||
totalOperations: operations.length,
|
||||
currentContentLength: file.getStringLength(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LazyStringFileData
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
const _ = require('lodash')
|
||||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
const OError = require('@overleaf/o-error')
|
||||
|
||||
const ot = require('../..')
|
||||
const File = ot.File
|
||||
@@ -202,4 +203,85 @@ describe('LazyStringFileData', function () {
|
||||
expect(fileData.hash).to.equal(stored.hash)
|
||||
expect(fileData.operations).to.deep.equal([])
|
||||
})
|
||||
|
||||
describe('error annotation', function () {
|
||||
it('annotates errors in toEager with blob and operation metadata', async function () {
|
||||
const testHash = this.fileHash
|
||||
// Create a file with content length 19 ('the quick brown fox')
|
||||
// then queue an operation that expects a different base length
|
||||
const fileData = new LazyStringFileData(testHash, undefined, 19)
|
||||
// This operation expects base length 999, which won't match the blob
|
||||
const badOp = new TextOperation()
|
||||
badOp.retain(999)
|
||||
fileData.operations.push(badOp)
|
||||
// Manually set stringLength to match the op's baseLength: pushing directly
|
||||
// to operations bypasses edit(), which is what normally updates stringLength
|
||||
fileData.stringLength = 999
|
||||
|
||||
try {
|
||||
await fileData.toEager(this.blobStore)
|
||||
expect.fail('should have thrown')
|
||||
} catch (err) {
|
||||
const info = OError.getFullInfo(err)
|
||||
expect(info).to.have.property('blobHash', testHash)
|
||||
expect(info).to.have.property('blobContentLength', 19)
|
||||
expect(info).to.have.property('metadataStringLength', 999)
|
||||
expect(info).to.have.property('totalOperations', 1)
|
||||
expect(info).to.have.property('operationIndex', 0)
|
||||
expect(info).to.have.property('currentContentLength', 19)
|
||||
expect(info).to.have.property('firstOpBaseLength', 999)
|
||||
expect(info).to.have.property('contentMatchesMetadata', false)
|
||||
expect(info).to.have.property('contentMatchesFirstOp', false)
|
||||
}
|
||||
})
|
||||
|
||||
it('annotates errors in edit with operation and metadata context', function () {
|
||||
const testHash = this.fileHash
|
||||
const fileData = new LazyStringFileData(testHash, undefined, 19)
|
||||
// Queue one valid operation first so totalExistingOperations > 0
|
||||
fileData.edit(new TextOperation().retain(19).insert('!'))
|
||||
// This operation expects base length 999, mismatching stringLength of 20
|
||||
const badOp = new TextOperation()
|
||||
badOp.retain(999)
|
||||
try {
|
||||
fileData.edit(badOp)
|
||||
expect.fail('should have thrown')
|
||||
} catch (err) {
|
||||
const info = OError.getFullInfo(err)
|
||||
expect(info).to.have.property('blobHash', testHash)
|
||||
expect(info).to.have.property('metadataStringLength', 20)
|
||||
expect(info).to.have.property('operationBaseLength', 999)
|
||||
expect(info).to.have.property('totalExistingOperations', 1)
|
||||
}
|
||||
})
|
||||
|
||||
it('annotates errors in applyOperations with the failing operation index', async function () {
|
||||
const testHash = this.fileHash
|
||||
// Content is 'the quick brown fox' (length 19)
|
||||
const fileData = new LazyStringFileData(testHash, undefined, 19)
|
||||
// First op is valid: insert at end
|
||||
const goodOp = new TextOperation().retain(19).insert('!')
|
||||
// Second op expects length 999 — will fail
|
||||
const badOp = new TextOperation()
|
||||
badOp.retain(999)
|
||||
fileData.operations.push(goodOp)
|
||||
fileData.operations.push(badOp)
|
||||
fileData.stringLength = 999
|
||||
|
||||
try {
|
||||
await fileData.toEager(this.blobStore)
|
||||
expect.fail('should have thrown')
|
||||
} catch (err) {
|
||||
const info = OError.getFullInfo(err)
|
||||
// The second operation (index 1) should be the one that fails
|
||||
expect(info).to.have.property('operationIndex', 1)
|
||||
expect(info).to.have.property('totalOperations', 2)
|
||||
expect(info).to.have.property('currentContentLength', 20)
|
||||
// toEager also tags with blob metadata
|
||||
expect(info).to.have.property('blobHash', testHash)
|
||||
expect(info).to.have.property('blobContentLength', 19)
|
||||
expect(info).to.have.property('metadataStringLength', 999)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user