ContentCacheManagerTests.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. const fs = require('fs')
  2. const Path = require('path')
  3. const { expect } = require('chai')
  4. const MODULE_PATH = '../../../app/js/ContentCacheManager'
  5. describe('ContentCacheManager', function () {
  6. let contentDir, pdfPath, xrefPath
  7. let ContentCacheManager, files, Settings
  8. before(function () {
  9. Settings = require('@overleaf/settings')
  10. ContentCacheManager = require(MODULE_PATH)
  11. })
  12. let contentRanges, newContentRanges, reclaimed
  13. async function run(filePath, pdfSize, pdfCachingMinChunkSize) {
  14. const result = await ContentCacheManager.promises.update({
  15. contentDir,
  16. filePath,
  17. pdfSize,
  18. pdfCachingMinChunkSize,
  19. compileTime: 1337,
  20. })
  21. let newlyReclaimed
  22. ;({
  23. contentRanges,
  24. newContentRanges,
  25. reclaimedSpace: newlyReclaimed,
  26. } = result)
  27. reclaimed += newlyReclaimed
  28. const fileNames = await fs.promises.readdir(contentDir)
  29. files = {}
  30. for (const fileName of fileNames) {
  31. const path = Path.join(contentDir, fileName)
  32. files[path] = await fs.promises.readFile(path)
  33. }
  34. }
  35. before(function () {
  36. contentDir =
  37. '/overleaf/services/clsi/output/602cee6f6460fca0ba7921e6/content/1797a7f48f9-5abc1998509dea1f'
  38. pdfPath =
  39. '/overleaf/services/clsi/output/602cee6f6460fca0ba7921e6/generated-files/1797a7f48ea-8ac6805139f43351/output.pdf'
  40. xrefPath =
  41. '/overleaf/services/clsi/output/602cee6f6460fca0ba7921e6/generated-files/1797a7f48ea-8ac6805139f43351/output.pdfxref'
  42. reclaimed = 0
  43. Settings.pdfCachingMinChunkSize = 1024
  44. })
  45. before(async function () {
  46. await fs.promises.rm(contentDir, { recursive: true, force: true })
  47. await fs.promises.mkdir(contentDir, { recursive: true })
  48. await fs.promises.mkdir(Path.dirname(pdfPath), { recursive: true })
  49. })
  50. describe('minimal', function () {
  51. const PATH_MINIMAL_PDF = 'test/acceptance/fixtures/minimal.pdf'
  52. const PATH_MINIMAL_XREF = 'test/acceptance/fixtures/minimal.pdfxref'
  53. const OBJECT_ID_1 = '9 0 '
  54. const HASH_LARGE =
  55. 'd7cfc73ad2fba4578a437517923e3714927bbf35e63ea88bd93c7a8076cf1fcd'
  56. const OBJECT_ID_2 = '10 0 '
  57. const HASH_SMALL =
  58. '896749b8343851b0dc385f71616916a7ba0434fcfb56d1fc7e27cd139eaa2f71'
  59. function getChunkPath(hash) {
  60. return Path.join('test/unit/js/snapshots/minimalCompile/chunks', hash)
  61. }
  62. let MINIMAL_SIZE, RANGE_1, RANGE_2, h1, h2, START_1, START_2, END_1, END_2
  63. before(async function () {
  64. await fs.promises.copyFile(PATH_MINIMAL_PDF, pdfPath)
  65. await fs.promises.copyFile(PATH_MINIMAL_XREF, xrefPath)
  66. const MINIMAL = await fs.promises.readFile(PATH_MINIMAL_PDF)
  67. MINIMAL_SIZE = (await fs.promises.stat(PATH_MINIMAL_PDF)).size
  68. RANGE_1 = await fs.promises.readFile(getChunkPath(HASH_LARGE))
  69. RANGE_2 = await fs.promises.readFile(getChunkPath(HASH_SMALL))
  70. h1 = HASH_LARGE
  71. h2 = HASH_SMALL
  72. START_1 = MINIMAL.indexOf(RANGE_1)
  73. END_1 = START_1 + RANGE_1.byteLength
  74. START_2 = MINIMAL.indexOf(RANGE_2)
  75. END_2 = START_2 + RANGE_2.byteLength
  76. })
  77. async function runWithMinimal(pdfCachingMinChunkSize) {
  78. await run(pdfPath, MINIMAL_SIZE, pdfCachingMinChunkSize)
  79. }
  80. describe('with two ranges qualifying', function () {
  81. before(async function () {
  82. await runWithMinimal(500)
  83. })
  84. it('should produce two ranges', function () {
  85. expect(contentRanges).to.have.length(2)
  86. })
  87. it('should find the correct offsets', function () {
  88. expect(contentRanges).to.deep.equal([
  89. {
  90. objectId: OBJECT_ID_1,
  91. start: START_1,
  92. end: END_1,
  93. hash: h1,
  94. },
  95. {
  96. objectId: OBJECT_ID_2,
  97. start: START_2,
  98. end: END_2,
  99. hash: h2,
  100. },
  101. ])
  102. })
  103. it('should store the contents', function () {
  104. expect(files).to.deep.equal({
  105. [Path.join(contentDir, h1)]: RANGE_1,
  106. [Path.join(contentDir, h2)]: RANGE_2,
  107. [Path.join(contentDir, '.state.v0.json')]: Buffer.from(
  108. JSON.stringify({
  109. hashAge: [
  110. [h1, 0],
  111. [h2, 0],
  112. ],
  113. hashSize: [
  114. [h1, RANGE_1.byteLength],
  115. [h2, RANGE_2.byteLength],
  116. ],
  117. })
  118. ),
  119. })
  120. })
  121. it('should mark all ranges as new', function () {
  122. expect(contentRanges).to.deep.equal(newContentRanges)
  123. })
  124. describe('when re-running with one range too small', function () {
  125. before(async function () {
  126. await runWithMinimal(1024)
  127. })
  128. it('should produce one range', function () {
  129. expect(contentRanges).to.have.length(1)
  130. })
  131. it('should find the correct offsets', function () {
  132. expect(contentRanges).to.deep.equal([
  133. {
  134. objectId: OBJECT_ID_1,
  135. start: START_1,
  136. end: END_1,
  137. hash: h1,
  138. },
  139. ])
  140. })
  141. it('should update the age of the 2nd range', function () {
  142. expect(files).to.deep.equal({
  143. [Path.join(contentDir, h1)]: RANGE_1,
  144. [Path.join(contentDir, h2)]: RANGE_2,
  145. [Path.join(contentDir, '.state.v0.json')]: Buffer.from(
  146. JSON.stringify({
  147. hashAge: [
  148. [h1, 0],
  149. [h2, 1],
  150. ],
  151. hashSize: [
  152. [h1, RANGE_1.byteLength],
  153. [h2, RANGE_2.byteLength],
  154. ],
  155. })
  156. ),
  157. })
  158. })
  159. it('should find no new ranges', function () {
  160. expect(newContentRanges).to.deep.equal([])
  161. })
  162. describe('when re-running 5 more times', function () {
  163. for (let i = 0; i < 5; i++) {
  164. before(async function () {
  165. await runWithMinimal(1024)
  166. })
  167. }
  168. it('should still produce one range', function () {
  169. expect(contentRanges).to.have.length(1)
  170. })
  171. it('should still find the correct offsets', function () {
  172. expect(contentRanges).to.deep.equal([
  173. {
  174. objectId: OBJECT_ID_1,
  175. start: START_1,
  176. end: END_1,
  177. hash: h1,
  178. },
  179. ])
  180. })
  181. it('should delete the 2nd range', function () {
  182. expect(files).to.deep.equal({
  183. [Path.join(contentDir, h1)]: RANGE_1,
  184. [Path.join(contentDir, '.state.v0.json')]: Buffer.from(
  185. JSON.stringify({
  186. hashAge: [[h1, 0]],
  187. hashSize: [[h1, RANGE_1.byteLength]],
  188. })
  189. ),
  190. })
  191. })
  192. it('should find no new ranges', function () {
  193. expect(newContentRanges).to.deep.equal([])
  194. })
  195. it('should yield the reclaimed space', function () {
  196. expect(reclaimed).to.equal(RANGE_2.byteLength)
  197. })
  198. })
  199. })
  200. })
  201. })
  202. })