From 876217695c3760bd569facb3ebdd525d6eafcfa3 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 4 Aug 2020 14:05:45 -0400 Subject: [PATCH] fix: truncate screenshot filenames using byteLength --- packages/server/lib/screenshots.js | 26 ++++++++++--------- packages/server/test/unit/screenshots_spec.js | 20 +++++++++++++- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/packages/server/lib/screenshots.js b/packages/server/lib/screenshots.js index d5974c6991bf..e75095747bf9 100644 --- a/packages/server/lib/screenshots.js +++ b/packages/server/lib/screenshots.js @@ -289,12 +289,24 @@ const getDimensions = function (details) { } const ensureUniquePath = function (withoutExt, extension, num = 0) { - const fullPath = num ? `${withoutExt} (${num}).${extension}` : `${withoutExt}.${extension}` + const suffix = `${num ? ` (${num})` : ''}.${extension}` + // many filesystems limit filename length to 255 bytes/characters, so truncate the filename to + // the smallest common denominator of safe filenames, which is 255 bytes + // @see https://github.com/cypress-io/cypress/issues/2403 + // @see https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits + const maxSafeBytes = 255 - suffix.length + const withoutExtBuf = Buffer.from(withoutExt) + + if (withoutExtBuf.byteLength > maxSafeBytes) { + withoutExt = withoutExtBuf.slice(0, maxSafeBytes).toString() + } + + const fullPath = [withoutExt, suffix].join('') return fs.pathExists(fullPath) .then((found) => { if (found) { - return ensureUniquePath(withoutExt, extension, (num += 1)) + return ensureUniquePath(withoutExt, extension, num + 1) } return fullPath @@ -323,18 +335,8 @@ const getPath = function (data, ext, screenshotsFolder) { .value() } - // truncate file names to be less than 220 characters - // to accomodate filename size limits - const maxFileNameLength = 220 const index = names.length - 1 - if (names[index].length > maxFileNameLength) { - names[index] = _.truncate(names[index], { - length: maxFileNameLength, - omission: '', - }) - } - // append (failed) to the last name if (data.testFailure) { names[index] = `${names[index]} (failed)` diff --git a/packages/server/test/unit/screenshots_spec.js b/packages/server/test/unit/screenshots_spec.js index e14c68da4c96..8864bc5fd655 100644 --- a/packages/server/test/unit/screenshots_spec.js +++ b/packages/server/test/unit/screenshots_spec.js @@ -618,6 +618,24 @@ describe('lib/screenshots', () => { }) }) + // @see https://github.com/cypress-io/cypress/issues/2403 + it('truncates long paths with unicode in them', async () => { + const fullPath = await screenshots.getPath({ + titles: [ + 'WMED: [STORY] Тестовые сценарии для CI', + 'Сценарии:', + 'Сценарий 2: Создание обращения, создание медзаписи, привязка обращения к медзаписи', + '- Сценарий 2', + ], + testFailure: true, + specName: 'WMED_UAT_Scenarios_For_CI_spec.js', + }, 'png', '/jenkins-slave/workspace/test-wmed/qa/cypress/wmed_ci/cypress/screenshots/') + + const basename = path.basename(fullPath) + + expect(Buffer.from(basename).byteLength).to.be.lessThan(255) + }) + _.each([Infinity, 0 / 0, [], {}, 1, false], (value) => { it(`doesn't err and stringifies non-string test title: ${value}`, () => { return screenshots.getPath({ @@ -632,7 +650,7 @@ describe('lib/screenshots', () => { }) }) - return _.each([null, undefined], (value) => { + _.each([null, undefined], (value) => { it(`doesn't err and removes null/undefined test title: ${value}`, () => { return screenshots.getPath({ specName: 'examples$/user/list.js',