Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(node): Enable native node tests #695

Merged
merged 36 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
feee95d
Move tests folder to _module
Soremwar Feb 2, 2021
4a21149
Fix format
Soremwar Feb 2, 2021
a5e1274
Add test setup
Soremwar Feb 3, 2021
c15be16
Working!
Soremwar Feb 3, 2021
7734aa2
Replace temporal file with Deno.Buffer for decompressing
Soremwar Feb 3, 2021
bc6f872
Remove linting and formatting for the suite
Soremwar Feb 3, 2021
3419e6f
Add runner
Soremwar Feb 3, 2021
28b9a74
Enable std/node/assert
Soremwar Feb 3, 2021
cd0a986
Refactor tests suites generation
Soremwar Feb 3, 2021
efd24cb
Fix runner
Soremwar Feb 3, 2021
b8baf3a
Fmt & Lint
Soremwar Feb 3, 2021
6444609
Enable test suite on CI
Soremwar Feb 3, 2021
589984f
Improve docs
Soremwar Feb 3, 2021
9f69ae5
Add test generation to CI
Soremwar Feb 3, 2021
0f850a4
Rename test folder to "testadata"
Soremwar Feb 3, 2021
68a2c79
Add .gitignore instructions for the test suites
Soremwar Feb 3, 2021
5721ce7
Add docs
Soremwar Feb 3, 2021
5484f9b
Add docs for modules
Soremwar Feb 3, 2021
3fa5213
Add copyright to generated files
Soremwar Feb 3, 2021
4525f75
Fix CI
Soremwar Feb 3, 2021
656843f
It's alivegit add .git add .
Soremwar Feb 3, 2021
fe55858
Fail test suite on failed test
Soremwar Feb 3, 2021
85c6c78
Cleanup comments
Soremwar Feb 3, 2021
c84b451
Define middle ground between not generated and not tested
Soremwar Feb 4, 2021
49e54cc
Convert script test runner to Deno.test
Soremwar Feb 4, 2021
50a74e0
Add docs
Soremwar Feb 4, 2021
4d47be7
Commit tests to std
Soremwar Feb 7, 2021
ff88eec
Adress comments
Soremwar Feb 7, 2021
6f140e3
Fix missing event export
Soremwar Feb 7, 2021
7f68c18
Remove the setup script from executing automatically
Soremwar Feb 7, 2021
32ac255
Use filename as test name
Soremwar Feb 9, 2021
070bfc0
Rename testdata to tools
Soremwar Feb 9, 2021
7962b80
Add comments about automatically generated files
Soremwar Feb 9, 2021
7988ed5
Fmt
Soremwar Feb 9, 2021
c2a9cd3
Rename
Soremwar Feb 9, 2021
13911e2
Accidentally changed process_exit fixture folder
Soremwar Feb 9, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ Examples of bad title:

Ensure there is a related issue and it is referenced in the PR text.

For contributions to the Node compatibility library please check the
[`std/node` contributing guide](./node/README.md)

_For maintainers_:

To release a new version a tag in the form of `x.y.z` should be added.
15 changes: 15 additions & 0 deletions node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,21 @@ const leftPad = require("left-pad");

## Contributing

### Setting up the test runner

This library contains automated tests pulled directly from the Node repo in
order ensure compatibility.

Setting up the test runner is as simple as running the `node/_tools/setup.ts`
file, this will pull the configured tests in and then add them to the test
workflow.

To enable new tests, simply add a new entry inside `node/_tools/config.json`
under the `tests` property. The structure this entries must have has to resemble
a path inside `https://github.com/nodejs/node/tree/master/test`.
Copy link
Member

@ry ry Feb 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered including Node as a git submodule? Is it too big?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A git submodule could suffice, but it makes it harder to patch some files that are needed for this to work, namely common/index.js.

That file is used in pretty much every single file, however it uses things like child_process that are not needed in most cases, so instead I just patch it and all the tests that are enabled continue to use it without any trouble

Not to mention this is more user friendly than submodules :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about mocking common/index.js by injecting a fake module into require.cache, using a technique described here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That might be more complicated than the current solution though @kt3k


### Best practices

When converting from promise-based to callback-based APIs, the most obvious way
is like this:

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions node/_tools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
versions
45 changes: 45 additions & 0 deletions node/_tools/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { join } from "../../path/mod.ts";

/**
* The test suite matches the folders inside the `test` folder inside the
* node repo
*
* Each test suite contains a list of files (which can be paths
* or a regex to match) that will be pulled from the node repo
*/
type TestSuites = Record<string, string[]>;

interface Config {
nodeVersion: string;
/** Ignored files won't regenerated by the update script */
ignore: TestSuites;
/**
* The files that will be run by the test suite
*
* The files to be generated with the update script must be listed here as well,
* but they won't be regenerated if they are listed in the `ignore` configuration
* */
tests: TestSuites;
suitesFolder: string;
versionsFolder: string;
}

export const config: Config = JSON.parse(
await Deno.readTextFile(new URL("./config.json", import.meta.url)),
);

export const ignoreList = Object.entries(config.ignore).reduce(
(total: RegExp[], [suite, paths]) => {
paths.forEach((path) => total.push(new RegExp(join(suite, path))));
return total;
},
[],
);

export const testList = Object.entries(config.tests).reduce(
(total: RegExp[], [suite, paths]) => {
paths.forEach((path) => total.push(new RegExp(join(suite, path))));
return total;
},
[],
);
15 changes: 15 additions & 0 deletions node/_tools/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"nodeVersion": "15.5.1",
"ignore": {
"common": [
"index.js"
]
},
"tests": {
"parallel": [
"test-event-emitter-listener-count.js"
]
},
"suitesFolder": "suites",
"versionsFolder": "versions"
}
23 changes: 23 additions & 0 deletions node/_tools/require.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createRequire } from "../module.ts";
import { isAbsolute } from "../../path/mod.ts";

/**
* This module is used as an entry point for each test file
*
* The idea is to emulate a CommonJS environment without having to modify
* the test files in any way
*
* Running with all permissions and unstable is recommended
*
* Usage: `deno run -A --unstable require.ts my_commonjs_file.js`
*/

const file = Deno.args[0];
if (!file) {
throw new Error("No file provided");
} else if (!isAbsolute(file)) {
throw new Error("Path for file must be absolute");
}

const require = createRequire(file);
require(file);
179 changes: 179 additions & 0 deletions node/_tools/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { gunzip } from "https://deno.land/x/[email protected]/gzip/gzip.ts";
import { Untar } from "../../archive/tar.ts";
import { walk } from "../../fs/walk.ts";
import { basename, fromFileUrl, join, resolve } from "../../path/mod.ts";
import { ensureFile } from "../../fs/ensure_file.ts";
import { config, ignoreList } from "./common.ts";

/**
* This script will download and extract the test files specified in the
* configuration file
*
* It will delete any previous tests unless they are specified on the `ignore`
* section of the configuration file
*
* Usage: `deno run --allow-read --allow-net --allow-write setup.ts`
*/

const NODE_URL = "https://nodejs.org/dist/vNODE_VERSION";
const NODE_FILE = "node-vNODE_VERSION.tar.gz";

/** URL for the download */
const url = `${NODE_URL}/${NODE_FILE}`.replaceAll(
"NODE_VERSION",
config.nodeVersion,
);
/** Local url location */
const path = join(
config.versionsFolder,
NODE_FILE.replaceAll("NODE_VERSION", config.nodeVersion),
);

/**
* This will overwrite the file if found
* */
async function downloadFile(url: string, path: string) {
console.log(`Downloading: ${url}...`);
const fileContent = await fetch(url)
.then((response) => {
if (response.ok) {
if (!response.body) {
throw new Error(
`The requested download url ${url} doesn't contain an archive to download`,
);
}
return response.body.getIterator();
} else if (response.status === 404) {
throw new Error(
`The requested version ${config.nodeVersion} could not be found for download`,
);
}
throw new Error(`Request failed with status ${response.status}`);
});

const filePath = fromFileUrl(new URL(path, import.meta.url));

await ensureFile(filePath);
const file = await Deno.open(filePath, {
truncate: true,
write: true,
});
for await (const chunk of fileContent) {
await Deno.write(file.rid, chunk);
}
file.close();
console.log(`Downloaded: ${url} into ${path}`);
}

async function clearTests() {
console.log("Cleaning up previous tests");

const files = walk(
(fromFileUrl(new URL(config.suitesFolder, import.meta.url))),
{
includeDirs: false,
skip: ignoreList,
},
);

for await (const file of files) {
await Deno.remove(file.path);
}
}

/**
* This will iterate over the ignore and test lists defined in the
* configuration file
*
* If it were to be found in the ignore list or not found in the test list, the
* function will return undefined, meaning the file won't be regenerated
*/
function getRequestedFileSuite(file: string): string | undefined {
for (const regex of ignoreList) {
if (regex.test(file)) {
return;
}
}

for (const suite in config.tests) {
for (const regex of config.tests[suite]) {
if (new RegExp(regex).test(file)) {
return suite;
}
}
}
}

async function decompressTests(filePath: string) {
console.log(`Processing ${basename(filePath)}...`);
const compressedFile = await Deno.open(
new URL(filePath, import.meta.url),
{ read: true },
);

const buffer = new Deno.Buffer(gunzip(await Deno.readAll(compressedFile)));
Deno.close(compressedFile.rid);

const tar = new Untar(buffer);

for await (const entry of tar) {
if (entry.type !== "file") continue;
const suite = getRequestedFileSuite(entry.fileName);
if (!suite) continue;
const path = resolve(
fromFileUrl(new URL(config.suitesFolder, import.meta.url)),
suite,
basename(entry.fileName),
);
await ensureFile(path);
const file = await Deno.open(path, {
create: true,
truncate: true,
write: true,
});
// This will allow CI to pass without checking linting and formatting
// on the test suite files, removing the need to mantain that as well
await Deno.writeAll(
file,
new TextEncoder().encode(
"// deno-fmt-ignore-file\n// deno-lint-ignore-file\n" +
"\n// Copyright Joyent and Node contributors. All rights reserved. MIT license.\n" +
`// Taken from Node ${config.nodeVersion}\n` +
'// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually\n\n',
),
);
await Deno.copy(entry, file);
Deno.close(file.rid);
}
}

let shouldDownload = false;
try {
Deno.lstatSync(new URL(path, import.meta.url)).isFile;
while (true) {
const r = (prompt(`File "${path}" found, use file? Y/N:`) ?? "").trim()
.toUpperCase();
if (r === "Y") {
break;
} else if (r === "N") {
shouldDownload = true;
break;
} else {
console.log(`Unexpected: "${r}"`);
}
}
} catch (e) {
if (!(e instanceof Deno.errors.NotFound)) {
throw e;
}
shouldDownload = true;
}

if (shouldDownload) {
console.log(`Downloading ${url} in path "${path}" ...`);
await downloadFile(url, path);
}

await clearTests();

await decompressTests(path);
Loading