From cb42cd31542ec3ce703dc9c4f80decd293ce5b42 Mon Sep 17 00:00:00 2001 From: Junxiao Shi Date: Fri, 9 Feb 2024 04:24:24 +0000 Subject: [PATCH] util: makeTmpDir test helper --- .../tests/segmented-object/test.t.ts | 8 ++-- .../cxx-tests/tests/keychain/verify/test.t.ts | 11 ++--- pkg/cli-common/package.json | 4 -- pkg/cli-common/tests/basic.t.ts | 11 +++-- pkg/keychain/package.json | 4 +- pkg/keychain/tests/store/keychain.t.ts | 33 +++++-------- pkg/ndnsec/package.json | 4 -- pkg/ndnsec/tests/keychain.t.ts | 17 +++---- pkg/node-transport/package.json | 4 +- pkg/node-transport/test-fixture/net-server.ts | 11 ++++- pkg/repo-api/package.json | 2 - pkg/repo-api/tests/data-tape.t.ts | 25 ++++------ pkg/repo-external/tests/pyrepo.t.ts | 2 +- pkg/segmented-object/tests/serve-fetch.t.ts | 7 +-- pkg/util/test-fixture/tmp.ts | 47 +++++++++++++++++++ pkg/util/test-fixture/tmpfile.ts | 33 ------------- 16 files changed, 107 insertions(+), 116 deletions(-) create mode 100644 pkg/util/test-fixture/tmp.ts delete mode 100644 pkg/util/test-fixture/tmpfile.ts diff --git a/integ/browser-tests/tests/segmented-object/test.t.ts b/integ/browser-tests/tests/segmented-object/test.t.ts index 9cbfa142..2d2f2526 100644 --- a/integ/browser-tests/tests/segmented-object/test.t.ts +++ b/integ/browser-tests/tests/segmented-object/test.t.ts @@ -2,7 +2,7 @@ import "./api"; import { makeObjectBody } from "@ndn/segmented-object/test-fixture/object-body"; import { delay, sha256, toHex } from "@ndn/util"; -import { deleteTmpFiles, writeTmpFile } from "@ndn/util/test-fixture/tmpfile"; +import { makeTmpDir } from "@ndn/util/test-fixture/tmp"; import { beforeAll, beforeEach, expect, test } from "vitest"; import { navigateToPage, page, pageInvoke } from "../../test-fixture/pptr"; @@ -13,8 +13,10 @@ let filename: string; beforeAll(async () => { objectBody = makeObjectBody(128 * 1024); objectBodyDigest = toHex(await sha256(objectBody)); - filename = writeTmpFile(objectBody); - return deleteTmpFiles; + + const tmpDir = makeTmpDir(); + filename = tmpDir.createFile(objectBody); + return tmpDir[Symbol.dispose]; }); beforeEach(() => navigateToPage(import.meta.url)); diff --git a/integ/cxx-tests/tests/keychain/verify/test.t.ts b/integ/cxx-tests/tests/keychain/verify/test.t.ts index 65edf496..415737c6 100644 --- a/integ/cxx-tests/tests/keychain/verify/test.t.ts +++ b/integ/cxx-tests/tests/keychain/verify/test.t.ts @@ -1,13 +1,11 @@ import { Certificate, EcCurve, ECDSA, generateSigningKey, RSA, RsaModulusLength, type SigningAlgorithm } from "@ndn/keychain"; import { Data } from "@ndn/packet"; import { Encoder } from "@ndn/tlv"; -import { deleteTmpFiles, writeTmpFile } from "@ndn/util/test-fixture/tmpfile"; -import { afterEach, expect, test } from "vitest"; +import { makeTmpDir } from "@ndn/util/test-fixture/tmp"; +import { expect, test } from "vitest"; import { execute } from "../../../test-fixture/cxxprogram"; -afterEach(deleteTmpFiles); - type Row = [ desc: string, algo: SigningAlgorithm, @@ -27,8 +25,9 @@ test.each(TABLE)("%s", async (desc, algo, genParam) => { const packet = new Data("/D", Uint8Array.of(0xC0, 0xC1)); await privateKey.sign(packet); - const certFile = writeTmpFile(Encoder.encode(cert.data)); - const packetFile = writeTmpFile(Encoder.encode(packet)); + using tmpDir = makeTmpDir(); + const certFile = tmpDir.createFile(Encoder.encode(cert.data)); + const packetFile = tmpDir.createFile(Encoder.encode(packet)); const { stdout } = await execute(import.meta.url, [certFile, packetFile]); const [certOk, packetOk] = stdout.split("\n"); diff --git a/pkg/cli-common/package.json b/pkg/cli-common/package.json index 0cd37962..6255560b 100644 --- a/pkg/cli-common/package.json +++ b/pkg/cli-common/package.json @@ -36,9 +36,5 @@ "env-var": "^7.4.1", "tslib": "^2.6.2", "wtfnode": "^0.9.1" - }, - "devDependencies": { - "@types/tmp": "^0.2.6", - "tmp": "^0.2.1" } } diff --git a/pkg/cli-common/tests/basic.t.ts b/pkg/cli-common/tests/basic.t.ts index 80593eb3..9c6710f6 100644 --- a/pkg/cli-common/tests/basic.t.ts +++ b/pkg/cli-common/tests/basic.t.ts @@ -4,21 +4,22 @@ import { generateSigningKey, KeyChain } from "@ndn/keychain"; import { FakeNfd } from "@ndn/nfdmgmt/test-fixture/prefix-reg"; import { Data, type Name } from "@ndn/packet"; import { Closers } from "@ndn/util"; -import { dirSync as tmpDir } from "tmp"; +import { makeTmpDir } from "@ndn/util/test-fixture/tmp"; import { afterAll, expect, test } from "vitest"; const closers = new Closers(); afterAll(closers.close); -const tmpKeyChain = tmpDir({ unsafeCleanup: true }); -afterAll(tmpKeyChain.removeCallback); +const tmpDir = makeTmpDir(); +closers.push(tmpDir); // `using tmpDir` seems to cause premature cleanup +const keyChainDir = tmpDir.join("keychain"); let signerName: Name; { - const keyChain = KeyChain.open(tmpKeyChain.name); + const keyChain = KeyChain.open(keyChainDir); const [signerPvt] = await generateSigningKey(keyChain, "/key-signer"); signerName = signerPvt.name; } -process.env.NDNTS_KEYCHAIN = tmpKeyChain.name; +process.env.NDNTS_KEYCHAIN = keyChainDir; process.env.NDNTS_KEY = "/key-signer"; const nfd = await new FakeNfd().open(); diff --git a/pkg/keychain/package.json b/pkg/keychain/package.json index eb2fb0b6..b58a64bf 100644 --- a/pkg/keychain/package.json +++ b/pkg/keychain/package.json @@ -42,8 +42,6 @@ }, "devDependencies": { "@types/b64-lite": "^1.4.2", - "@types/tmp": "^0.2.6", - "b64-lite": "^1.4.0", - "tmp": "^0.2.1" + "b64-lite": "^1.4.0" } } \ No newline at end of file diff --git a/pkg/keychain/tests/store/keychain.t.ts b/pkg/keychain/tests/store/keychain.t.ts index cab15e4a..1604c45f 100644 --- a/pkg/keychain/tests/store/keychain.t.ts +++ b/pkg/keychain/tests/store/keychain.t.ts @@ -1,8 +1,8 @@ import "@ndn/packet/test-fixture/expect"; import { Component, Data, digestSigning, KeyLocator, Name } from "@ndn/packet"; -import { dirSync as tmpDir } from "tmp"; -import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; +import { makeTmpDir } from "@ndn/util/test-fixture/tmp"; +import { beforeAll, describe, expect, test, vi } from "vitest"; import { Certificate, CertNaming, generateSigningKey, KeyChain, type NamedSigner, type NamedVerifier, SigningAlgorithmListFull, ValidityPeriod } from "../.."; import * as TestCertStore from "../../test-fixture/cert-store"; @@ -20,25 +20,18 @@ test("temp CertStore", async () => { TestCertStore.check(record); }); -describe("persistent", () => { - let locator: string; - beforeEach(async () => { - const d = tmpDir({ unsafeCleanup: true }); - locator = d.name; - return d.removeCallback; - }); - - test("persistent KeyStore", async () => { - const keyChain = KeyChain.open(locator, SigningAlgorithmListFull); - const record = await TestKeyStore.execute(keyChain); - TestKeyStore.check(record); - }); +test("persistent KeyStore", async () => { + using tmpDir = makeTmpDir(); + const keyChain = KeyChain.open(tmpDir.name, SigningAlgorithmListFull); + const record = await TestKeyStore.execute(keyChain); + TestKeyStore.check(record); +}); - test("persistent CertStore", async () => { - const keyChain = KeyChain.open(locator, SigningAlgorithmListFull); - const record = await TestCertStore.execute(keyChain); - TestCertStore.check(record); - }); +test("persistent CertStore", async () => { + using tmpDir = makeTmpDir(); + const keyChain = KeyChain.open(tmpDir.name, SigningAlgorithmListFull); + const record = await TestCertStore.execute(keyChain); + TestCertStore.check(record); }); describe("getSigner", () => { diff --git a/pkg/ndnsec/package.json b/pkg/ndnsec/package.json index da1318c0..70387d5d 100644 --- a/pkg/ndnsec/package.json +++ b/pkg/ndnsec/package.json @@ -32,9 +32,5 @@ "@yoursunny/asn1": "0.0.20200718", "execa": "^8.0.1", "tslib": "^2.6.2" - }, - "devDependencies": { - "@types/tmp": "^0.2.6", - "tmp": "^0.2.1" } } \ No newline at end of file diff --git a/pkg/ndnsec/tests/keychain.t.ts b/pkg/ndnsec/tests/keychain.t.ts index 2280ea4a..06c19073 100644 --- a/pkg/ndnsec/tests/keychain.t.ts +++ b/pkg/ndnsec/tests/keychain.t.ts @@ -1,26 +1,21 @@ import * as TestCertStore from "@ndn/keychain/test-fixture/cert-store"; import * as TestKeyStore from "@ndn/keychain/test-fixture/key-store"; -import { dirSync as tmpDir } from "tmp"; -import { beforeEach, test } from "vitest"; +import { makeTmpDir } from "@ndn/util/test-fixture/tmp"; +import { test } from "vitest"; import { NdnsecKeyChain } from ".."; -let home: string; -beforeEach(() => { - const { name, removeCallback } = tmpDir({ unsafeCleanup: true }); - home = name; - return removeCallback; -}); - test.runIf(NdnsecKeyChain.supported)("KeyStore", async () => { + using tmpDir = makeTmpDir(); const enabled: TestKeyStore.Enable = { HMAC: false, Ed25519: false }; - const keyChain = new NdnsecKeyChain({ home }); + const keyChain = new NdnsecKeyChain({ home: tmpDir.name }); const record = await TestKeyStore.execute(keyChain, enabled); TestKeyStore.check(record, enabled); }, 20000); test.runIf(NdnsecKeyChain.supported)("CertStore", async () => { - const keyChain = new NdnsecKeyChain({ home }); + using tmpDir = makeTmpDir(); + const keyChain = new NdnsecKeyChain({ home: tmpDir.name }); const record = await TestCertStore.execute(keyChain); TestCertStore.check(record); }, 20000); diff --git a/pkg/node-transport/package.json b/pkg/node-transport/package.json index 82735abf..0d7424d8 100644 --- a/pkg/node-transport/package.json +++ b/pkg/node-transport/package.json @@ -36,10 +36,8 @@ "@ndn/fw": "workspace:*", "@ndn/packet": "workspace:*", "@ndn/util": "workspace:*", - "@types/tmp": "^0.2.6", "@types/url-format-lax": "^2.0.3", "@types/url-parse-lax": "^5.0.2", - "streaming-iterables": "^8.0.1", - "tmp": "^0.2.1" + "streaming-iterables": "^8.0.1" } } \ No newline at end of file diff --git a/pkg/node-transport/test-fixture/net-server.ts b/pkg/node-transport/test-fixture/net-server.ts index eba95ea8..95474e65 100644 --- a/pkg/node-transport/test-fixture/net-server.ts +++ b/pkg/node-transport/test-fixture/net-server.ts @@ -1,7 +1,7 @@ import { type EventEmitter, once } from "node:events"; import net from "node:net"; -import { tmpNameSync } from "tmp"; +import { makeTmpDir, type TmpDir } from "@ndn/util/test-fixture/tmp"; /** * Transport test server. @@ -127,7 +127,14 @@ export class IpcServer extends NetServer { /** Unix/IPC server path. */ public readonly path = process.platform === "win32" ? `//./pipe/2a8370be-8abc-448f-bb09-54d8b243cf7a/${Math.trunc(Math.random() * 0x100000000)}` : - tmpNameSync(); + (this.tmpDir = makeTmpDir()).filename(); + + private tmpDir?: TmpDir; + + public override [Symbol.asyncDispose](): Promise { + this.tmpDir?.[Symbol.dispose](); + return super[Symbol.asyncDispose](); + } protected override listenBegin(): void { this.server.listen(this.path); diff --git a/pkg/repo-api/package.json b/pkg/repo-api/package.json index 1bd8a380..c1a65f2d 100644 --- a/pkg/repo-api/package.json +++ b/pkg/repo-api/package.json @@ -42,9 +42,7 @@ }, "devDependencies": { "@ndn/node-transport": "workspace:*", - "@types/tmp": "^0.2.6", "stream-mock": "^2.0.5", - "tmp": "^0.2.1", "type-fest": "^4.10.2" } } \ No newline at end of file diff --git a/pkg/repo-api/tests/data-tape.t.ts b/pkg/repo-api/tests/data-tape.t.ts index d3ff8005..6faa2fae 100644 --- a/pkg/repo-api/tests/data-tape.t.ts +++ b/pkg/repo-api/tests/data-tape.t.ts @@ -6,9 +6,9 @@ import { BufferBreaker } from "@ndn/node-transport/test-fixture/buffer-breaker"; import { Data, Interest, Name } from "@ndn/packet"; import { Encoder } from "@ndn/tlv"; import { Closers, delay, randomJitter } from "@ndn/util"; +import { makeTmpDir } from "@ndn/util/test-fixture/tmp"; import { BufferReadableMock, BufferWritableMock } from "stream-mock"; import { collect } from "streaming-iterables"; -import { dirSync as tmpDir } from "tmp"; import { beforeEach, describe, expect, test, vi } from "vitest"; import { BulkInsertInitiator, BulkInsertTarget, copy, DataTape } from ".."; @@ -65,23 +65,16 @@ describe("DataTape reader", () => { }); }); -describe("DataTape file", () => { - let dir: string; - beforeEach(() => { - const d = tmpDir({ unsafeCleanup: true }); - dir = d.name; - return d.removeCallback; - }); +test("DataTape file copy", async () => { + using tmpDir = makeTmpDir(); - test("copy", async () => { - const tapeA = new DataTape(`${dir}/A.dtar`); - await copy(new DataTape(makeDataTapeReadStream), tapeA); - await expect(collect(tapeA.listNames())).resolves.toHaveLength(500); + const tapeA = new DataTape(tmpDir.join("A.dtar")); + await copy(new DataTape(makeDataTapeReadStream), tapeA); + await expect(collect(tapeA.listNames())).resolves.toHaveLength(500); - const tapeB = new DataTape(`${dir}/B.dtar`); - await copy(tapeA, new Name("/A/2"), tapeB); - await expect(collect(tapeB.listNames())).resolves.toHaveLength(100); - }); + const tapeB = new DataTape(tmpDir.join("B.dtar")); + await copy(tapeA, new Name("/A/2"), tapeB); + await expect(collect(tapeB.listNames())).resolves.toHaveLength(100); }); async function testBulkInsertTarget( diff --git a/pkg/repo-external/tests/pyrepo.t.ts b/pkg/repo-external/tests/pyrepo.t.ts index e51047c1..1e68b340 100644 --- a/pkg/repo-external/tests/pyrepo.t.ts +++ b/pkg/repo-external/tests/pyrepo.t.ts @@ -4,7 +4,7 @@ import { Endpoint } from "@ndn/endpoint"; import { Forwarder } from "@ndn/fw"; import { Segment } from "@ndn/naming-convention2"; import { Data, digestSigning, Name } from "@ndn/packet"; -import { makeTmpDir } from "@ndn/util/test-fixture/tmpfile"; +import { makeTmpDir } from "@ndn/util/test-fixture/tmp"; import { expect, test } from "vitest"; import { PyRepoStore } from ".."; diff --git a/pkg/segmented-object/tests/serve-fetch.t.ts b/pkg/segmented-object/tests/serve-fetch.t.ts index 45d65cc5..3145101c 100644 --- a/pkg/segmented-object/tests/serve-fetch.t.ts +++ b/pkg/segmented-object/tests/serve-fetch.t.ts @@ -9,7 +9,7 @@ import { Bridge } from "@ndn/l3face"; import { Segment2, Segment3 } from "@ndn/naming-convention2"; import { Data, FwHint, Name, type Verifier } from "@ndn/packet"; import { Closers, delay } from "@ndn/util"; -import { deleteTmpFiles, writeTmpFile } from "@ndn/util/test-fixture/tmpfile"; +import { makeTmpDir } from "@ndn/util/test-fixture/tmp"; import { BufferReadableMock, BufferWritableMock } from "stream-mock"; import { collect, consume } from "streaming-iterables"; import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; @@ -75,8 +75,9 @@ test("stream to stream", async () => { describe("file source", () => { let filename: string; beforeAll(() => { - filename = writeTmpFile(objectBody); - return deleteTmpFiles; + const tmpDir = makeTmpDir(); + filename = tmpDir.createFile(objectBody); + return tmpDir[Symbol.dispose]; }); test("file to buffer", async () => { diff --git a/pkg/util/test-fixture/tmp.ts b/pkg/util/test-fixture/tmp.ts new file mode 100644 index 00000000..3665dde7 --- /dev/null +++ b/pkg/util/test-fixture/tmp.ts @@ -0,0 +1,47 @@ +import path from "node:path"; + +import { dirSync as tmpDir, tmpNameSync as tmpName } from "tmp"; +import { sync as write } from "write"; + +/** Create a temporary directory. */ +export function makeTmpDir(): TmpDir { + const { name, removeCallback } = tmpDir({ prefix: "tmp-NDNts-", unsafeCleanup: true }); + const filename = () => tmpName({ dir: name }); + return { + name, + join: (...segments) => path.join(name, ...segments), + filename, + createFile: (content) => { + const fn = filename(); + write(fn, content); + return fn; + }, + [Symbol.dispose]: removeCallback, + }; +} + +/** + * Temporary directory. + * + * @remarks + * Disposing this object deletes the directory and its content. + */ +export interface TmpDir extends Disposable { + /** Directory path. */ + readonly name: string; + + /** Join with additional path segment(s). */ + join: (...segments: string[]) => string; + + /** + * Generate random filename within the directory. + * @returns Full filename. + */ + filename: () => string; + + /** + * Write content to a file within the directory. + * @returns Full filename. + */ + createFile: (content: string | Uint8Array) => string; +} diff --git a/pkg/util/test-fixture/tmpfile.ts b/pkg/util/test-fixture/tmpfile.ts deleted file mode 100644 index 691162ff..00000000 --- a/pkg/util/test-fixture/tmpfile.ts +++ /dev/null @@ -1,33 +0,0 @@ -import path from "node:path"; - -import { dirSync as tmpDir, fileSync as tmpFile } from "tmp"; -import { sync as write } from "write"; - -const removeCallbacks: Array<() => void> = []; - -export function writeTmpFile(content: string | Uint8Array): string { - const { name, removeCallback } = tmpFile(); - removeCallbacks.push(removeCallback); - write(name, content); - return name; -} - -export function deleteTmpFiles() { - for (const f of removeCallbacks) { - f(); - } -} - -export function makeTmpDir(): TmpDir { - const { name, removeCallback } = tmpDir({ unsafeCleanup: true }); - return { - name, - join: (...segments) => path.join(name, ...segments), - [Symbol.dispose]: removeCallback, - }; -} - -export interface TmpDir extends Disposable { - name: string; - join: (...segments: string[]) => string; -}