Merge pull request #3153 from github/koesie10/yauzl-unzip-tests

Add simple tests for `yauzl`-based unzip functions
This commit is contained in:
Koen Vlaswinkel 2023-12-20 10:01:45 +01:00 коммит произвёл GitHub
Родитель ef8bf9fd1b f1fe4a20f2
Коммит e824fda9e7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 203 добавлений и 7 удалений

Просмотреть файл

@ -91,18 +91,23 @@ export async function readDirFullPaths(path: string): Promise<string[]> {
* Symbolic links are ignored.
*
* @param dir the directory to walk
* @param includeDirectories whether to include directories in the results
*
* @return An iterator of the full path to all files recursively found in the directory.
*/
export async function* walkDirectory(
dir: string,
includeDirectories = false,
): AsyncIterableIterator<string> {
const seenFiles = new Set<string>();
for await (const d of await opendir(dir)) {
const entry = join(dir, d.name);
seenFiles.add(entry);
if (d.isDirectory()) {
yield* walkDirectory(entry);
if (includeDirectories) {
yield entry;
}
yield* walkDirectory(entry, includeDirectories);
} else if (d.isFile()) {
yield entry;
}

Просмотреть файл

@ -31,12 +31,7 @@ export function readZipEntries(zipFile: ZipFile): Promise<ZipEntry[]> {
zipFile.readEntry();
zipFile.on("entry", (entry: ZipEntry) => {
if (/\/$/.test(entry.fileName)) {
// Directory file names end with '/'
// We don't need to do anything for directories.
} else {
files.push(entry);
}
files.push(entry);
zipFile.readEntry();
});

Просмотреть файл

@ -0,0 +1,196 @@
import { createHash } from "crypto";
import { open } from "fs/promises";
import { join, relative, resolve, sep } from "path";
import { chmod, pathExists, readdir } from "fs-extra";
import { dir, DirectoryResult } from "tmp-promise";
import {
excludeDirectories,
openZip,
openZipBuffer,
readZipEntries,
unzipToDirectory,
} from "../../../src/common/unzip";
import { walkDirectory } from "../../../src/common/files";
const zipPath = resolve(__dirname, "../data/unzip/test-zip.zip");
describe("openZip", () => {
it("can open a zip file", async () => {
const zipFile = await openZip(zipPath, {
lazyEntries: false,
});
expect(zipFile.entryCount).toEqual(11);
});
});
describe("readZipEntries", () => {
it("can read the entries when there are multiple directories", async () => {
const zipFile = await openZip(zipPath, {
lazyEntries: true,
});
const entries = await readZipEntries(zipFile);
expect(entries.map((entry) => entry.fileName).sort()).toEqual([
"directory/",
"directory/file.txt",
"directory/file2.txt",
"directory2/",
"directory2/file.txt",
"empty-directory/",
"tools/",
"tools/osx64/",
"tools/osx64/java-aarch64/",
"tools/osx64/java-aarch64/bin/",
"tools/osx64/java-aarch64/bin/java",
]);
});
});
describe("excludeDirectories", () => {
it("excludes directories", async () => {
const zipFile = await openZip(zipPath, {
lazyEntries: true,
});
const entries = await readZipEntries(zipFile);
const entriesWithoutDirectories = excludeDirectories(entries);
expect(
entriesWithoutDirectories.map((entry) => entry.fileName).sort(),
).toEqual([
"directory/file.txt",
"directory/file2.txt",
"directory2/file.txt",
"tools/osx64/java-aarch64/bin/java",
]);
});
});
describe("openZipBuffer", () => {
it("can read an entry in the zip file", async () => {
const zipFile = await openZip(zipPath, {
lazyEntries: true,
autoClose: false,
});
const entries = await readZipEntries(zipFile);
const entry = entries.find(
(entry) => entry.fileName === "directory/file.txt",
);
expect(entry).toBeDefined();
if (!entry) {
return;
}
const buffer = await openZipBuffer(zipFile, entry);
expect(buffer).toHaveLength(13);
expect(buffer.toString("utf8")).toEqual("I am a file\n\n");
});
});
describe("unzipToDirectory", () => {
let tmpDir: DirectoryResult;
beforeEach(async () => {
tmpDir = await dir({
unsafeCleanup: true,
});
});
afterEach(async () => {
for await (const file of walkDirectory(tmpDir.path)) {
await chmod(file, 0o777);
}
await tmpDir?.cleanup();
});
it("extracts all files", async () => {
await unzipToDirectory(zipPath, tmpDir.path);
const allFiles = [];
for await (const file of walkDirectory(tmpDir.path, true)) {
allFiles.push(file);
}
expect(
allFiles
.map((filePath) => relative(tmpDir.path, filePath).split(sep).join("/"))
.sort(),
).toEqual([
"directory",
"directory/file.txt",
"directory/file2.txt",
"directory2",
"directory2/file.txt",
"empty-directory",
"tools",
"tools/osx64",
"tools/osx64/java-aarch64",
"tools/osx64/java-aarch64/bin",
"tools/osx64/java-aarch64/bin/java",
]);
await expectFile(join(tmpDir.path, "directory", "file.txt"), {
mode: 0o100644,
contents: "I am a file\n\n",
});
await expectFile(join(tmpDir.path, "directory", "file2.txt"), {
mode: 0o100644,
contents: "I am another file\n\n",
});
await expectFile(join(tmpDir.path, "directory2", "file.txt"), {
mode: 0o100600,
contents: "I am secret\n",
});
await expectFile(
join(tmpDir.path, "tools", "osx64", "java-aarch64", "bin", "java"),
{
mode: 0o100755,
hash: "68b832b5c0397c5baddbbb0a76cf5407b7ea5eee8f84f9ab9488f04a52e529eb",
},
);
expect(await pathExists(join(tmpDir.path, "empty-directory"))).toBe(true);
expect(await readdir(join(tmpDir.path, "empty-directory"))).toEqual([]);
});
});
async function expectFile(
filePath: string,
{
mode: expectedMode,
hash: expectedHash,
contents: expectedContents,
}: {
mode: number;
hash?: string;
contents?: string;
},
) {
const file = await open(filePath, "r");
// Windows doesn't really support file modes
if (process.platform !== "win32") {
const stats = await file.stat();
expect(stats.mode).toEqual(expectedMode);
}
const contents = await file.readFile();
if (expectedHash) {
const hash = await computeHash(contents);
expect(hash).toEqual(expectedHash);
}
if (expectedContents) {
expect(contents.toString("utf-8")).toEqual(expectedContents);
}
}
async function computeHash(contents: Buffer) {
const hash = createHash("sha256");
hash.update(contents);
return hash.digest("hex");
}

Двоичные данные
extensions/ql-vscode/test/unit-tests/data/unzip/test-zip.zip Normal file

Двоичный файл не отображается.