ContentCacheManagerTests.js 7.0 KB

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