mirror of
https://github.com/yt-dlp/ejs.git
synced 2026-06-13 00:32:11 +00:00
Make tests and solving runtime agnostic
This commit is contained in:
10
package.json
Normal file
10
package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"astring": "1.9.0",
|
||||
"meriyah": "6.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rollup": "4.49.0"
|
||||
}
|
||||
}
|
||||
91
src/main.ts
91
src/main.ts
@@ -1,15 +1,13 @@
|
||||
import { writeAll } from "@std/io";
|
||||
import { read, write } from "./io.ts";
|
||||
|
||||
import { getFromPrepared, preprocessPlayer } from "./solvers.ts";
|
||||
import { isOneOf } from "./utils.ts";
|
||||
|
||||
async function main(): Promise<Output> {
|
||||
if (Deno.stdin.isTerminal()) {
|
||||
throw "Expected input on stdin";
|
||||
}
|
||||
const input: Input = await new Response(Deno.stdin.readable).json();
|
||||
const preprocessedPlayer = input.type === "player"
|
||||
? preprocessPlayer(input.player)
|
||||
: input.preprocessed_player;
|
||||
function main(input: Input): Output {
|
||||
const preprocessedPlayer =
|
||||
input.type === "player"
|
||||
? preprocessPlayer(input.player)
|
||||
: input.preprocessed_player;
|
||||
const solvers = getFromPrepared(preprocessedPlayer);
|
||||
|
||||
const responses = input.requests.map(
|
||||
@@ -18,7 +16,7 @@ async function main(): Promise<Output> {
|
||||
return {
|
||||
type: "error",
|
||||
request,
|
||||
error: `Failed to extract ${request.type} function`,
|
||||
error: `Unknown request type: ${request.type}`,
|
||||
};
|
||||
}
|
||||
const solver = solvers[request.type];
|
||||
@@ -39,10 +37,13 @@ async function main(): Promise<Output> {
|
||||
return {
|
||||
type: "error",
|
||||
request,
|
||||
error: `${error}`,
|
||||
error:
|
||||
error instanceof Error
|
||||
? `${error.message}\n${error.stack}`
|
||||
: `${error}`,
|
||||
};
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const output: Output = {
|
||||
@@ -55,33 +56,33 @@ async function main(): Promise<Output> {
|
||||
return output;
|
||||
}
|
||||
|
||||
async function safeMain(): Promise<Output> {
|
||||
async function safeMain(): Promise<void> {
|
||||
try {
|
||||
return await main();
|
||||
const input = await read();
|
||||
const output = main(input);
|
||||
await write(output);
|
||||
} catch (error) {
|
||||
return {
|
||||
await write({
|
||||
type: "error",
|
||||
error: `${error}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const output = await safeMain();
|
||||
const bytes = new TextEncoder().encode(JSON.stringify(output));
|
||||
await writeAll(Deno.stdout, bytes);
|
||||
safeMain();
|
||||
|
||||
type Input =
|
||||
export type Input =
|
||||
| {
|
||||
type: "player";
|
||||
player: string;
|
||||
requests: JsChallengeRequest[];
|
||||
output_preprocessed: boolean;
|
||||
}
|
||||
type: "player";
|
||||
player: string;
|
||||
requests: JsChallengeRequest[];
|
||||
output_preprocessed: boolean;
|
||||
}
|
||||
| {
|
||||
type: "preprocessed";
|
||||
preprocessed_player: string;
|
||||
requests: JsChallengeRequest[];
|
||||
};
|
||||
type: "preprocessed";
|
||||
preprocessed_player: string;
|
||||
requests: JsChallengeRequest[];
|
||||
};
|
||||
|
||||
type JsChallengeRequest = {
|
||||
type: string;
|
||||
@@ -92,23 +93,23 @@ type JsChallengeRequest = {
|
||||
|
||||
type JsChallengeProviderResponse =
|
||||
| {
|
||||
type: "result";
|
||||
request: JsChallengeRequest;
|
||||
response: string;
|
||||
}
|
||||
type: "result";
|
||||
request: JsChallengeRequest;
|
||||
response: string;
|
||||
}
|
||||
| {
|
||||
type: "error";
|
||||
request: JsChallengeRequest;
|
||||
error: string;
|
||||
};
|
||||
type: "error";
|
||||
request: JsChallengeRequest;
|
||||
error: string;
|
||||
};
|
||||
|
||||
type Output =
|
||||
export type Output =
|
||||
| {
|
||||
type: "result";
|
||||
preprocessed_player?: string;
|
||||
responses: JsChallengeProviderResponse[];
|
||||
}
|
||||
type: "result";
|
||||
preprocessed_player?: string;
|
||||
responses: JsChallengeProviderResponse[];
|
||||
}
|
||||
| {
|
||||
type: "error";
|
||||
error: string;
|
||||
};
|
||||
type: "error";
|
||||
error: string;
|
||||
};
|
||||
|
||||
121
src/setup.ts
121
src/setup.ts
@@ -3,122 +3,7 @@ import { parse } from "meriyah";
|
||||
export const setupNodes = parse(`
|
||||
globalThis.XMLHttpRequest = { prototype: {} };
|
||||
const window = Object.assign(Object.create(null), globalThis);
|
||||
window.location = new URL("https://www.youtube.com/watch?v=yt-dlp-wins");
|
||||
const document = {};
|
||||
`).body || [
|
||||
{
|
||||
type: "ExpressionStatement",
|
||||
expression: {
|
||||
type: "AssignmentExpression",
|
||||
left: {
|
||||
type: "MemberExpression",
|
||||
object: {
|
||||
type: "Identifier",
|
||||
name: "globalThis",
|
||||
},
|
||||
computed: false,
|
||||
property: {
|
||||
type: "Identifier",
|
||||
name: "XMLHttpRequest",
|
||||
},
|
||||
optional: false,
|
||||
},
|
||||
operator: "=",
|
||||
right: {
|
||||
type: "ObjectExpression",
|
||||
properties: [
|
||||
{
|
||||
type: "Property",
|
||||
key: {
|
||||
type: "Identifier",
|
||||
name: "prototype",
|
||||
},
|
||||
value: {
|
||||
type: "ObjectExpression",
|
||||
properties: [],
|
||||
},
|
||||
kind: "init",
|
||||
computed: false,
|
||||
method: false,
|
||||
shorthand: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "VariableDeclaration",
|
||||
kind: "const",
|
||||
declarations: [
|
||||
{
|
||||
type: "VariableDeclarator",
|
||||
id: {
|
||||
type: "Identifier",
|
||||
name: "window",
|
||||
},
|
||||
init: {
|
||||
type: "CallExpression",
|
||||
callee: {
|
||||
type: "MemberExpression",
|
||||
object: {
|
||||
type: "Identifier",
|
||||
name: "Object",
|
||||
},
|
||||
computed: false,
|
||||
property: {
|
||||
type: "Identifier",
|
||||
name: "assign",
|
||||
},
|
||||
optional: false,
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: "CallExpression",
|
||||
callee: {
|
||||
type: "MemberExpression",
|
||||
object: {
|
||||
type: "Identifier",
|
||||
name: "Object",
|
||||
},
|
||||
computed: false,
|
||||
property: {
|
||||
type: "Identifier",
|
||||
name: "create",
|
||||
},
|
||||
optional: false,
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: "Literal",
|
||||
value: null,
|
||||
},
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
{
|
||||
type: "Identifier",
|
||||
name: "globalThis",
|
||||
},
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "VariableDeclaration",
|
||||
kind: "const",
|
||||
declarations: [
|
||||
{
|
||||
type: "VariableDeclarator",
|
||||
id: {
|
||||
type: "Identifier",
|
||||
name: "document",
|
||||
},
|
||||
init: {
|
||||
type: "ObjectExpression",
|
||||
properties: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
let self = globalThis;
|
||||
`).body;
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
import { assertStrictEquals } from "@std/assert";
|
||||
import { getFromPrepared, preprocessPlayer } from "./solvers.ts";
|
||||
import { players, tests } from "../tests/tests.ts";
|
||||
import { getCachePath } from "../tests/utils.ts";
|
||||
import { getIO } from "../tests/io.ts";
|
||||
|
||||
const io = await getIO();
|
||||
|
||||
for (const test of tests) {
|
||||
for (const variant of test.variants ?? players.keys()) {
|
||||
const path = getCachePath(test.player, variant);
|
||||
Deno.test(`${test.player} ${variant}`, async (t) => {
|
||||
const content = await Deno.readTextFile(path);
|
||||
await io.test(`${test.player} ${variant}`, async (assert, subtest) => {
|
||||
const content = await io.read(path);
|
||||
const solvers = getFromPrepared(preprocessPlayer(content));
|
||||
for (const mode of ["nsig", "sig"] as const) {
|
||||
for (const step of test[mode] || []) {
|
||||
await t.step(`${step.input} (${mode})`, () => {
|
||||
await subtest(`${step.input} (${mode})`, () => {
|
||||
const got = solvers[mode]?.(step.input);
|
||||
assertStrictEquals(got, step.expected);
|
||||
});
|
||||
assert.equal(got, step.expected);
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import { exists } from "@std/fs/exists";
|
||||
import { players, tests } from "./tests.ts";
|
||||
import { getCachePath } from "./utils.ts";
|
||||
import { getIO } from "./io.ts";
|
||||
|
||||
const io = await getIO();
|
||||
|
||||
for (const test of tests) {
|
||||
const variants = test.variants ?? players.keys();
|
||||
for (const variant of variants) {
|
||||
const path = getCachePath(test.player, variant);
|
||||
if (await exists(path)) {
|
||||
if (await io.exists(path)) {
|
||||
continue;
|
||||
}
|
||||
const playerPath = players.get(variant);
|
||||
@@ -17,10 +19,6 @@ for (const test of tests) {
|
||||
console.error(`Failed to request ${variant} player for ${test.player}`);
|
||||
continue;
|
||||
}
|
||||
const file = await Deno.open(path, {
|
||||
createNew: true,
|
||||
write: true,
|
||||
});
|
||||
response.body!.pipeTo(file.writable);
|
||||
await io.write(path, response);
|
||||
}
|
||||
}
|
||||
|
||||
134
tests/io.ts
Normal file
134
tests/io.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
type IO = {
|
||||
exists(path: string): Promise<boolean>;
|
||||
read(path: string): Promise<string>;
|
||||
write(path: string, response: Response): Promise<void>;
|
||||
test: Test;
|
||||
};
|
||||
|
||||
type Assert = {
|
||||
equal<T>(actual: T, expected: T, message?: string): void;
|
||||
};
|
||||
type Test = (name: string, func: TestFunc) => Promise<void>;
|
||||
type TestFunc = (assert: Assert, subtest: Subtest) => Promise<void> | void;
|
||||
type Subtest = (name: string, func: SubtestFunc) => Promise<void>;
|
||||
type SubtestFunc = (assert: Assert) => Promise<void> | void;
|
||||
|
||||
let io: IO | null = null;
|
||||
|
||||
export async function getIO(): Promise<IO> {
|
||||
if (io === null) {
|
||||
io = await _getIO();
|
||||
}
|
||||
return io;
|
||||
}
|
||||
|
||||
async function _getIO(): Promise<IO> {
|
||||
if (typeof Deno !== "undefined") {
|
||||
const { exists } = await import("@std/fs/exists");
|
||||
const { assertStrictEquals } = await import("@std/assert");
|
||||
const assert = {
|
||||
equal<T>(actual: T, expected: T, message?: string) {
|
||||
return assertStrictEquals(actual, expected, message);
|
||||
},
|
||||
};
|
||||
return {
|
||||
exists,
|
||||
read(path: string): Promise<string> {
|
||||
return Deno.readTextFile(path);
|
||||
},
|
||||
async write(path: string, response: Response): Promise<void> {
|
||||
const file = await Deno.open(path, {
|
||||
createNew: true,
|
||||
write: true,
|
||||
});
|
||||
response.body!.pipeTo(file.writable);
|
||||
},
|
||||
test(name: string, func: TestFunc) {
|
||||
Deno.test(name, (t) => {
|
||||
return func(assert, async (name, func): Promise<void> => {
|
||||
await t.step(name, () => {
|
||||
return func(assert);
|
||||
});
|
||||
});
|
||||
});
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
} else if (typeof Bun !== "undefined") {
|
||||
const { expect, test } = await import("bun:test");
|
||||
const { access } = await import("node:fs/promises");
|
||||
const assert = {
|
||||
equal<T>(actual: T, expected: T, message?: string) {
|
||||
return expect(actual).toBe(expected, message);
|
||||
},
|
||||
};
|
||||
return {
|
||||
async exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
read(path: string): Promise<string> {
|
||||
return Bun.file(path).text();
|
||||
},
|
||||
write(path: string, response: Response): Promise<void> {
|
||||
return Bun.write(path, response);
|
||||
},
|
||||
test(name: string, func: TestFunc) {
|
||||
test(name, () => {
|
||||
// XXX: how to do subtests
|
||||
return func(assert, async (name, func): Promise<void> => {
|
||||
await func(assert);
|
||||
});
|
||||
});
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
} else if (
|
||||
typeof navigator === "object" &&
|
||||
navigator.userAgent.startsWith("Node.js")
|
||||
) {
|
||||
const { suite, test } = await import("node:test");
|
||||
const { readFile, writeFile, access } = await import("node:fs/promises");
|
||||
const { deepStrictEqual } = await import("node:assert");
|
||||
const assert: Assert = {
|
||||
equal<T>(actual: T, expected: T, message?: string): void {
|
||||
deepStrictEqual(actual, expected, message);
|
||||
},
|
||||
};
|
||||
return {
|
||||
async exists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
read(path: string): Promise<string> {
|
||||
return readFile(path, { encoding: "utf-8" });
|
||||
},
|
||||
write(path: string, response: Response): Promise<void> {
|
||||
return writeFile(path, response.body!);
|
||||
},
|
||||
test(name: string, func: TestFunc): Promise<void> {
|
||||
suite(name, () => {
|
||||
return func(assert, async (name, func): Promise<void> => {
|
||||
await test(name, async () => {
|
||||
await func(assert);
|
||||
});
|
||||
});
|
||||
});
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error(
|
||||
`unsupported runtime for testing${
|
||||
navigator.userAgent ? `: ${navigator.userAgent}` : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user