From 3d1d7806c3c3122fd8b4b90462f25dd3f1b963d3 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Wed, 19 May 2021 13:53:19 +0000 Subject: [PATCH 1/4] chore(lru-cache): add scaffolding for lru-cache --- packages/lru-cache/AmazonChangeLog.txt | 5 +++ packages/lru-cache/LICENSE | 36 ++++++++++++++++++++ packages/lru-cache/README.md | 17 ++++++++++ packages/lru-cache/jest.config.js | 5 +++ packages/lru-cache/package.json | 44 +++++++++++++++++++++++++ packages/lru-cache/src/LRUCache.spec.ts | 5 +++ packages/lru-cache/src/LRUCache.ts | 1 + packages/lru-cache/src/index.ts | 1 + packages/lru-cache/tsconfig.cjs.json | 10 ++++++ packages/lru-cache/tsconfig.es.json | 11 +++++++ 10 files changed, 135 insertions(+) create mode 100644 packages/lru-cache/AmazonChangeLog.txt create mode 100644 packages/lru-cache/LICENSE create mode 100644 packages/lru-cache/README.md create mode 100644 packages/lru-cache/jest.config.js create mode 100644 packages/lru-cache/package.json create mode 100644 packages/lru-cache/src/LRUCache.spec.ts create mode 100644 packages/lru-cache/src/LRUCache.ts create mode 100644 packages/lru-cache/src/index.ts create mode 100644 packages/lru-cache/tsconfig.cjs.json create mode 100644 packages/lru-cache/tsconfig.es.json diff --git a/packages/lru-cache/AmazonChangeLog.txt b/packages/lru-cache/AmazonChangeLog.txt new file mode 100644 index 000000000000..eee9cde71cc8 --- /dev/null +++ b/packages/lru-cache/AmazonChangeLog.txt @@ -0,0 +1,5 @@ +The Open Source MIT licensed project is at: https://github.com/Yomguithereal/mnemonist + +High level modifications: +- The source code is written in TypeScript. +- An option is added to delete a cache key. \ No newline at end of file diff --git a/packages/lru-cache/LICENSE b/packages/lru-cache/LICENSE new file mode 100644 index 000000000000..b8ca37d6edf2 --- /dev/null +++ b/packages/lru-cache/LICENSE @@ -0,0 +1,36 @@ +The MIT License (MIT) + +Copyright (c) 2016 Guillaume Plique (Yomguithereal) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/packages/lru-cache/README.md b/packages/lru-cache/README.md new file mode 100644 index 000000000000..4286b78e2691 --- /dev/null +++ b/packages/lru-cache/README.md @@ -0,0 +1,17 @@ +# @aws-sdk/lru-cache + +[![NPM version](https://img.shields.io/npm/v/@aws-sdk/lru-cache/latest.svg)](https://www.npmjs.com/package/@aws-sdk/lru-cache) +[![NPM downloads](https://img.shields.io/npm/dm/@aws-sdk/lru-cache.svg)](https://www.npmjs.com/package/@aws-sdk/lru-cache) + +> An internal package + +## Usage + +You probably shouldn't, at least directly. + +## LRUCache + +This is a fork of LRUCache in [mnemonist](https://github.com/Yomguithereal/mnemonist) with high level modifications: + +- The source code is written in TypeScript. +- An option is added to delete a cache key. diff --git a/packages/lru-cache/jest.config.js b/packages/lru-cache/jest.config.js new file mode 100644 index 000000000000..a8d1c2e49912 --- /dev/null +++ b/packages/lru-cache/jest.config.js @@ -0,0 +1,5 @@ +const base = require("../../jest.config.base.js"); + +module.exports = { + ...base, +}; diff --git a/packages/lru-cache/package.json b/packages/lru-cache/package.json new file mode 100644 index 000000000000..34293488d4a6 --- /dev/null +++ b/packages/lru-cache/package.json @@ -0,0 +1,44 @@ +{ + "name": "@aws-sdk/lru-cache", + "version": "3.0.0", + "scripts": { + "prepublishOnly": "yarn build && downlevel-dts dist/types dist/types/ts3.4", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build:es": "tsc -p tsconfig.es.json", + "build": "yarn build:es && yarn build:cjs", + "test": "jest --passWithNoTests" + }, + "author": { + "name": "AWS SDK for JavaScript Team", + "url": "https://aws.amazon.com/javascript/" + }, + "license": "MIT", + "main": "./dist/cjs/index.js", + "module": "./dist/es/index.js", + "types": "./dist/types/index.d.ts", + "dependencies": { + "tslib": "^2.0.0" + }, + "devDependencies": { + "@types/jest": "^26.0.4", + "@types/node": "^10.0.0", + "jest": "^26.1.0", + "typescript": "~4.2.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "typesVersions": { + "<4.0": { + "types/*": [ + "types/ts3.4/*" + ] + } + }, + "homepage": "https://github.com/aws/aws-sdk-js-v3/tree/main/packages/lru-cache", + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-js-v3.git", + "directory": "packages/lru-cache" + } +} diff --git a/packages/lru-cache/src/LRUCache.spec.ts b/packages/lru-cache/src/LRUCache.spec.ts new file mode 100644 index 000000000000..347505ffd889 --- /dev/null +++ b/packages/lru-cache/src/LRUCache.spec.ts @@ -0,0 +1,5 @@ +import { LRUCache } from "./LRUCache"; + +describe(LRUCache.name, () => { + it("test", () => {}); +}); diff --git a/packages/lru-cache/src/LRUCache.ts b/packages/lru-cache/src/LRUCache.ts new file mode 100644 index 000000000000..bb2fada36beb --- /dev/null +++ b/packages/lru-cache/src/LRUCache.ts @@ -0,0 +1 @@ +export class LRUCache {} diff --git a/packages/lru-cache/src/index.ts b/packages/lru-cache/src/index.ts new file mode 100644 index 000000000000..0f06d662a9a7 --- /dev/null +++ b/packages/lru-cache/src/index.ts @@ -0,0 +1 @@ +export * from "./LRUCache"; diff --git a/packages/lru-cache/tsconfig.cjs.json b/packages/lru-cache/tsconfig.cjs.json new file mode 100644 index 000000000000..67f09ddd4138 --- /dev/null +++ b/packages/lru-cache/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "declarationDir": "./dist/types", + "rootDir": "./src", + "outDir": "./dist/cjs", + "baseUrl": "." + }, + "extends": "../../tsconfig.cjs.json", + "include": ["src/"] +} diff --git a/packages/lru-cache/tsconfig.es.json b/packages/lru-cache/tsconfig.es.json new file mode 100644 index 000000000000..c5f23ee06fb2 --- /dev/null +++ b/packages/lru-cache/tsconfig.es.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "lib": ["es5", "es2015.promise", "es2015.iterable"], + "declarationDir": "./dist/types", + "rootDir": "./src", + "outDir": "./dist/es", + "baseUrl": "." + }, + "extends": "../../tsconfig.es.json", + "include": ["src/"] +} From 182d3ebd19ec57304dd72d5dc5f3b27005a06e94 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Wed, 19 May 2021 16:43:10 +0000 Subject: [PATCH 2/4] feat(lru-cache): add typed-array based LRUCache implementation --- packages/lru-cache/src/LRUCache.spec.ts | 135 +++++++++++++- packages/lru-cache/src/LRUCache.ts | 169 +++++++++++++++++- packages/lru-cache/src/utils/PointerArray.ts | 7 + .../utils/getPointerArrayConstructor.spec.ts | 32 ++++ .../src/utils/getPointerArrayConstructor.ts | 15 ++ 5 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 packages/lru-cache/src/utils/PointerArray.ts create mode 100644 packages/lru-cache/src/utils/getPointerArrayConstructor.spec.ts create mode 100644 packages/lru-cache/src/utils/getPointerArrayConstructor.ts diff --git a/packages/lru-cache/src/LRUCache.spec.ts b/packages/lru-cache/src/LRUCache.spec.ts index 347505ffd889..acc84eedff70 100644 --- a/packages/lru-cache/src/LRUCache.spec.ts +++ b/packages/lru-cache/src/LRUCache.spec.ts @@ -1,5 +1,138 @@ import { LRUCache } from "./LRUCache"; describe(LRUCache.name, () => { - it("test", () => {}); + describe("should throw if given an invalid capacity", () => { + [undefined, {}, -1, true, 1.01, Infinity].forEach((capacity) => { + it(`invalid capacity: ${capacity}`, () => { + expect(() => { + // @ts-ignore + new LRUCache(capacity); + }).toThrow("@aws-sdk/lru-cache: capacity should be a finite positive integer."); + }); + }); + }); + + describe("should create LRUCache for valid capacity", () => { + it.each([1, 5, 1000, Math.pow(2, 8) - 1, Math.pow(2, 16) - 1, Math.pow(2, 32) - 1])( + "valid capacity: %d", + (capacity) => { + const cache = new LRUCache(capacity); + expect(cache.capacity).toStrictEqual(capacity); + } + ); + }); + + it("drops least recently used value when LRUCache is full", () => { + const cache = new LRUCache(2); + expect(cache.capacity).toStrictEqual(2); + + cache.set("one", 1); + expect(cache.size).toStrictEqual(1); + expect(cache.has("one")).toStrictEqual(true); + expect(cache.has("two")).toStrictEqual(false); + expect(cache.has("three")).toStrictEqual(false); + + cache.set("two", 2); + expect(cache.size).toStrictEqual(2); + expect(cache.has("one")).toStrictEqual(true); + expect(cache.has("two")).toStrictEqual(true); + expect(cache.has("three")).toStrictEqual(false); + + cache.set("three", 3); + expect(cache.size).toStrictEqual(2); + expect(cache.has("one")).toStrictEqual(false); + expect(cache.has("two")).toStrictEqual(true); + expect(cache.has("three")).toStrictEqual(true); + }); + + it("clears LRUCache", () => { + const cache = new LRUCache(2); + expect(cache.capacity).toStrictEqual(2); + + cache.set("one", 1); + cache.set("two", 2); + expect(cache.size).toStrictEqual(2); + expect(cache.has("one")).toStrictEqual(true); + expect(cache.has("two")).toStrictEqual(true); + + cache.clear(); + expect(cache.size).toStrictEqual(0); + expect(cache.has("one")).toStrictEqual(false); + expect(cache.has("two")).toStrictEqual(false); + }); + + it("peek does not modify ordering of the key", () => { + const cache = new LRUCache(2); + expect(cache.capacity).toStrictEqual(2); + + cache.set("one", 1); + cache.set("two", 2); + + // Does not modify ordering of the key, and will be evicted when cache is full. + cache.peek("one"); + + cache.set("three", 3); + expect(cache.has("one")).toStrictEqual(false); + expect(cache.has("two")).toStrictEqual(true); + expect(cache.has("three")).toStrictEqual(true); + }); + + it("get splays key to the top", () => { + const cache = new LRUCache(2); + expect(cache.capacity).toStrictEqual(2); + + cache.set("one", 1); + cache.set("two", 2); + + // Does modify ordering of the key, and "two" will be evicted when cache is full. + cache.get("one"); + + cache.set("three", 3); + expect(cache.has("one")).toStrictEqual(true); + expect(cache.has("two")).toStrictEqual(false); + expect(cache.has("three")).toStrictEqual(true); + }); + + it("delete removes key from cache", () => { + const cache = new LRUCache(3); + expect(cache.capacity).toStrictEqual(3); + + cache.set("one", 1); + cache.set("two", 2); + cache.set("three", 3); + expect(cache.size).toStrictEqual(3); + expect(cache.has("one")).toStrictEqual(true); + expect(cache.has("two")).toStrictEqual(true); + expect(cache.has("three")).toStrictEqual(true); + + // Delete head + cache.delete("three"); + expect(cache.size).toStrictEqual(2); + expect(cache.has("three")).toStrictEqual(false); + + cache.set("three", 3); + expect(cache.has("three")).toStrictEqual(true); + + // Delete node which is neither head or tail + cache.delete("two"); + expect(cache.size).toStrictEqual(2); + expect(cache.has("two")).toStrictEqual(false); + + // Delete tail + cache.delete("one"); + expect(cache.size).toStrictEqual(1); + expect(cache.has("one")).toStrictEqual(false); + + // Delete the only key + cache.delete("three"); + expect(cache.size).toStrictEqual(0); + + cache.set("one", 1); + cache.set("two", 2); + cache.set("three", 3); + expect(cache.size).toStrictEqual(3); + expect(cache.has("one")).toStrictEqual(true); + expect(cache.has("two")).toStrictEqual(true); + expect(cache.has("three")).toStrictEqual(true); + }); }); diff --git a/packages/lru-cache/src/LRUCache.ts b/packages/lru-cache/src/LRUCache.ts index bb2fada36beb..1b065547fe26 100644 --- a/packages/lru-cache/src/LRUCache.ts +++ b/packages/lru-cache/src/LRUCache.ts @@ -1 +1,168 @@ -export class LRUCache {} +import { getPointerArrayConstructor } from "./utils/getPointerArrayConstructor"; +import { PointerArray } from "./utils/PointerArray"; + +export class LRUCache { + private forward: PointerArray; + private backward: PointerArray; + private deleted: PointerArray; + + private keys: K[]; + private values: V[]; + + public size = 0; + private deletedSize = 0; + private head = 0; + private tail = 0; + + private items: Record = {} as Record; + + constructor(public readonly capacity: number) { + if (typeof capacity !== "number" || !isFinite(capacity) || Math.floor(capacity) !== capacity || capacity <= 0) + throw new Error("@aws-sdk/lru-cache: capacity should be a finite positive integer."); + + const PointerArrayCtor = getPointerArrayConstructor(capacity); + this.forward = new PointerArrayCtor(capacity); + this.backward = new PointerArrayCtor(capacity); + this.deleted = new PointerArrayCtor(capacity); + + this.keys = new Array(capacity); + this.values = new Array(capacity); + } + + /** + * Method used to splay a value on top. + */ + private splayOnTop(pointer: number) { + const oldHead = this.head; + + if (this.head === pointer) return this; + + const previous = this.backward[pointer]; + const next = this.forward[pointer]; + + if (this.tail === pointer) { + this.tail = previous; + } else { + this.backward[next] = previous; + } + + this.forward[previous] = next; + + this.backward[oldHead] = pointer; + this.head = pointer; + this.forward[pointer] = oldHead; + + return this; + } + + /** + * Method used to set the value for the given key in the cache. + */ + set(key: K, value: V) { + // The key already exists, we just need to update the value and splay on top + let pointer = this.items[key]; + + if (typeof pointer !== "undefined") { + this.splayOnTop(pointer); + this.values[pointer] = value; + return; + } + + // The cache is not yet full + if (this.size < this.capacity) { + if (this.deletedSize > 0) { + pointer = this.deleted[--this.deletedSize]; + } else { + pointer = this.size; + } + this.size++; + } else { + // Cache is full, we need to drop the last value + pointer = this.tail; + this.tail = this.backward[pointer]; + delete this.items[this.keys[pointer]]; + } + + // Storing key & value + this.items[key] = pointer; + this.keys[pointer] = key; + this.values[pointer] = value; + + // Moving the item at the front of the list + this.forward[pointer] = this.head; + this.backward[this.head] = pointer; + this.head = pointer; + } + + /** + * Method used to get the value attached to the given key. Will move the + * related key to the front of the underlying linked list. + */ + get(key: K) { + const pointer = this.items[key]; + if (typeof pointer === "undefined") return; + this.splayOnTop(pointer); + return this.values[pointer]; + } + + /** + * Method used to get the value attached to the given key. Does not modify + * the ordering of the underlying linked list. + */ + peek(key: K) { + const pointer = this.items[key]; + if (typeof pointer === "undefined") return; + return this.values[pointer]; + } + + /** + * Method used to check whether the key exists in the cache. + */ + has(key: K) { + return key in this.items; + } + + /** + * Method used to delete the value for the given key in the cache. + */ + delete(key: K) { + const pointer = this.items[key]; + + if (typeof pointer === "undefined") { + return; + } + + if (this.head === pointer && this.tail === pointer) { + this.clear(); + return; + } + + const previous = this.backward[pointer]; + const next = this.forward[pointer]; + + if (this.head === pointer) { + this.head = next; + } + if (this.tail === pointer) { + this.tail = previous; + } + + this.forward[previous] = next; + this.backward[next] = previous; + + delete this.items[key]; + this.size--; + this.deleted[this.deletedSize++] = pointer; + } + + /** + * Method used to clear the structure. + */ + clear() { + this.size = 0; + this.deletedSize = 0; + this.head = 0; + this.tail = 0; + this.items = {} as Record; + } +} diff --git a/packages/lru-cache/src/utils/PointerArray.ts b/packages/lru-cache/src/utils/PointerArray.ts new file mode 100644 index 000000000000..b9e49a84876e --- /dev/null +++ b/packages/lru-cache/src/utils/PointerArray.ts @@ -0,0 +1,7 @@ +export type PointerArrayConstructor = + | Uint8ArrayConstructor + | Uint16ArrayConstructor + | Uint32ArrayConstructor + | Float64ArrayConstructor; + +export type PointerArray = Uint8Array | Uint16Array | Uint32Array | Float64Array; diff --git a/packages/lru-cache/src/utils/getPointerArrayConstructor.spec.ts b/packages/lru-cache/src/utils/getPointerArrayConstructor.spec.ts new file mode 100644 index 000000000000..14e68ac6457c --- /dev/null +++ b/packages/lru-cache/src/utils/getPointerArrayConstructor.spec.ts @@ -0,0 +1,32 @@ +import { getPointerArrayConstructor } from "./getPointerArrayConstructor"; +import { PointerArrayConstructor } from "./PointerArray"; + +describe(getPointerArrayConstructor.name, () => { + const validatePointerArrayConstructor = (min: number, max: number, expectedCtor: PointerArrayConstructor) => { + it(`returns ${expectedCtor} for ${min}`, () => { + expect(getPointerArrayConstructor(min)).toStrictEqual(expectedCtor); + }); + it(`returns ${expectedCtor} for ${(max - min) / 2}`, () => { + expect(getPointerArrayConstructor((max - min) / 2)).toStrictEqual(expectedCtor); + }); + it(`returns ${expectedCtor} for ${max}`, () => { + expect(getPointerArrayConstructor(max)).toStrictEqual(expectedCtor); + }); + }; + + describe("returns Uint8Array for capacity <= Math.pow(2, 8)", () => { + validatePointerArrayConstructor(0, Math.pow(2, 8), Uint8Array); + }); + + describe("returns Uint16Array for Math.pow(2, 8) < capacity <= Math.pow(2, 16)", () => { + validatePointerArrayConstructor(Math.pow(2, 8) + 1, Math.pow(2, 16), Uint16Array); + }); + + describe("returns Uint32Array for Math.pow(2, 16) < capacity <= Math.pow(2, 32)", () => { + validatePointerArrayConstructor(Math.pow(2, 16) + 1, Math.pow(2, 32), Uint32Array); + }); + + describe("returns Float64Array for capacity > Math.pow(2, 32)", () => { + expect(getPointerArrayConstructor(Math.pow(2, 32) + 1)).toStrictEqual(Float64Array); + }); +}); diff --git a/packages/lru-cache/src/utils/getPointerArrayConstructor.ts b/packages/lru-cache/src/utils/getPointerArrayConstructor.ts new file mode 100644 index 000000000000..418d515048c4 --- /dev/null +++ b/packages/lru-cache/src/utils/getPointerArrayConstructor.ts @@ -0,0 +1,15 @@ +import { PointerArrayConstructor } from "./PointerArray"; + +const MAX_8BIT_INTEGER = Math.pow(2, 8) - 1; +const MAX_16BIT_INTEGER = Math.pow(2, 16) - 1; +const MAX_32BIT_INTEGER = Math.pow(2, 32) - 1; + +export const getPointerArrayConstructor = (size: number): PointerArrayConstructor => { + const maxIndex = size - 1; + + if (maxIndex <= MAX_8BIT_INTEGER) return Uint8Array; + if (maxIndex <= MAX_16BIT_INTEGER) return Uint16Array; + if (maxIndex <= MAX_32BIT_INTEGER) return Uint32Array; + + return Float64Array; +}; From 25b12994c16112789537d87b41b37034f77ee97e Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Wed, 19 May 2021 16:50:50 +0000 Subject: [PATCH 3/4] chore: remove LRUCache heading in README --- packages/lru-cache/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/lru-cache/README.md b/packages/lru-cache/README.md index 4286b78e2691..607667972d91 100644 --- a/packages/lru-cache/README.md +++ b/packages/lru-cache/README.md @@ -9,8 +9,6 @@ You probably shouldn't, at least directly. -## LRUCache - This is a fork of LRUCache in [mnemonist](https://github.com/Yomguithereal/mnemonist) with high level modifications: - The source code is written in TypeScript. From 407f1bf0ded58525f4249abe1c8f731f4b5e88f0 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 20 May 2021 14:49:24 +0000 Subject: [PATCH 4/4] fix: mock getPointerArrayConstructor in LRUCache tests --- packages/lru-cache/src/LRUCache.spec.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/lru-cache/src/LRUCache.spec.ts b/packages/lru-cache/src/LRUCache.spec.ts index acc84eedff70..d5e95c4c9004 100644 --- a/packages/lru-cache/src/LRUCache.spec.ts +++ b/packages/lru-cache/src/LRUCache.spec.ts @@ -1,6 +1,17 @@ import { LRUCache } from "./LRUCache"; +import { getPointerArrayConstructor } from "./utils/getPointerArrayConstructor"; + +jest.mock("./utils/getPointerArrayConstructor"); describe(LRUCache.name, () => { + beforeEach(() => { + (getPointerArrayConstructor as jest.Mock).mockReturnValue(Array); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + describe("should throw if given an invalid capacity", () => { [undefined, {}, -1, true, 1.01, Infinity].forEach((capacity) => { it(`invalid capacity: ${capacity}`, () => {