frida-test 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -116,10 +116,10 @@ frida-test -i org.owasp.mastestapp.MASTestApp-iOS ./tests/ios ./tests/shared
116
116
 
117
117
  `frida-test` automatically bundles the test suites, and compiles them together with the testing framework into a Frida agent.
118
118
 
119
- If you only want to compile this agent, use `frida-test-compiler`:
119
+ If you only want to compile this agent, use `frida-test-compile`:
120
120
 
121
121
  ```sh
122
- frida-test-compiler [options] <dir...>
122
+ frida-test-compile [options] <dir...>
123
123
  ```
124
124
 
125
125
  | Option | Description |
@@ -127,12 +127,12 @@ frida-test-compiler [options] <dir...>
127
127
  | `-o, --out <path>` | Path of the output file for JSON reporter (default: disabled) |
128
128
  | `-h, --help` | Shows the help message |
129
129
 
130
- ### `frida-test-compiler` Examples
130
+ ### `frida-test-compile` Examples
131
131
 
132
132
  ```sh
133
133
  # Collects all tests in ./tests, compiles the frida-test agent, and print it to stdout
134
- frida-test-compiler ./tests
134
+ frida-test-compile ./tests
135
135
 
136
136
  # Collects all tests in ./tests, compile the frida-test agent, and stores it in the path ./frida-test-agent.js
137
- frida-test-compiler ./tests -o ./frida-test-agent.js
137
+ frida-test-compile ./tests -o ./frida-test-agent.js
138
138
  ```
package/dist/bundler.js CHANGED
@@ -1,15 +1,15 @@
1
- import { execFile } from "node:child_process";
1
+ import { execFileSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
- import { copyFile, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { promisify } from "node:util";
5
+ import { fileURLToPath } from "node:url";
6
6
  import { logger } from "./logger.js";
7
7
  const IMPORT_MARKER = "/// IMPORT TESTS SUITES ///";
8
- const execFileAsync = promisify(execFile);
9
8
  const AGENT_ENTRYPOINT_FILENAME = "agentRuntime.ts";
10
- const AGENT_ENTRYPOINT_BASENAME = path.basename(AGENT_ENTRYPOINT_FILENAME, path.extname(AGENT_ENTRYPOINT_FILENAME));
9
+ const AGENT_ENTRYPOINT_BASENAME = path.parse(AGENT_ENTRYPOINT_FILENAME).name;
11
10
  const AGENT_BUNDLE_FILENAME = `${AGENT_ENTRYPOINT_BASENAME}.bundle.js`;
12
- function resolveProjectRoot(startDir = process.cwd()) {
11
+ function getProjectRoot() {
12
+ const startDir = process.cwd();
13
13
  let dir = startDir;
14
14
  while (true) {
15
15
  if (existsSync(path.join(dir, "package.json")))
@@ -20,45 +20,40 @@ function resolveProjectRoot(startDir = process.cwd()) {
20
20
  dir = parent;
21
21
  }
22
22
  }
23
- function resolveAgentRuntimePath(projectRoot) {
24
- return path.join(projectRoot, "src", "agent-runtime");
25
- }
26
- function resolveFridaCompileBin(projectRoot) {
27
- const binName = process.platform === "win32" ? "frida-compile.cmd" : "frida-compile";
28
- return path.join(projectRoot, "node_modules", ".bin", binName);
29
- }
30
- let cachedProjectRoot;
31
- let cachedAgentRuntimeSrcDir;
32
- let cachedFridaCompileBin;
33
- function getProjectRoot() {
34
- cachedProjectRoot ??= resolveProjectRoot();
35
- return cachedProjectRoot;
36
- }
37
- function getAgentRuntimeSrcDir(projectRoot) {
38
- if (!cachedAgentRuntimeSrcDir) {
39
- const dir = resolveAgentRuntimePath(projectRoot);
40
- if (!existsSync(dir)) {
41
- throw new Error(`Agent runtime source directory not found at "${dir}".`);
23
+ function getPackageRoot() {
24
+ let dir = path.dirname(fileURLToPath(import.meta.url));
25
+ while (true) {
26
+ if (existsSync(path.join(dir, "package.json")))
27
+ return dir;
28
+ const parent = path.dirname(dir);
29
+ if (parent === dir) {
30
+ throw new Error("Could not locate frida-test's own package.json.");
42
31
  }
43
- cachedAgentRuntimeSrcDir = dir;
32
+ dir = parent;
44
33
  }
45
- return cachedAgentRuntimeSrcDir;
46
34
  }
47
- function getFridaCompileBin(projectRoot) {
48
- if (!cachedFridaCompileBin) {
49
- const localBin = resolveFridaCompileBin(projectRoot);
50
- cachedFridaCompileBin = existsSync(localBin) ? { path: localBin, useLocal: true } : { path: "npx", useLocal: false };
35
+ function getAgentRuntimeSrcDir() {
36
+ const dir = path.join(getPackageRoot(), "src", "agent-runtime");
37
+ if (!existsSync(dir)) {
38
+ throw new Error(`Agent runtime source directory not found at "${dir}".`);
51
39
  }
52
- return cachedFridaCompileBin;
40
+ return dir;
41
+ }
42
+ function getFridaCompileBin(projectRoot) {
43
+ const binName = process.platform === "win32" ? "frida-compile.cmd" : "frida-compile";
44
+ const localBin = path.join(projectRoot, "node_modules", ".bin", binName);
45
+ return existsSync(localBin) ? { path: localBin, useLocal: true } : { path: "npx", useLocal: false };
53
46
  }
54
47
  async function createWorkDir(projectRoot) {
55
- const cacheRoot = path.join(projectRoot, ".frida-test-cache");
56
- await mkdir(cacheRoot, { recursive: true });
57
- const gitignorePath = path.join(cacheRoot, ".gitignore");
48
+ const workdirRoot = path.join(projectRoot, ".frida-test-cache");
49
+ await mkdir(workdirRoot, { recursive: true });
50
+ const gitignorePath = path.join(workdirRoot, ".gitignore");
58
51
  if (!existsSync(gitignorePath)) {
59
52
  await writeFile(gitignorePath, "*\n", "utf8");
60
53
  }
61
- return mkdtemp(cacheRoot + path.sep);
54
+ const workdir = await mkdtemp(workdirRoot + path.sep);
55
+ logger.info(`Temporary workdir created at ${workdir}`);
56
+ return workdir;
62
57
  }
63
58
  async function deleteWorkDir(workDir) {
64
59
  try {
@@ -68,19 +63,6 @@ async function deleteWorkDir(workDir) {
68
63
  logger.warn(`Failed to remove temporary work dir "${workDir}": ${err.message}`);
69
64
  }
70
65
  }
71
- async function copyDirRecursive(srcDir, destDir) {
72
- await mkdir(destDir, { recursive: true });
73
- const entries = await readdir(srcDir, { withFileTypes: true });
74
- await Promise.all(entries.map(async (entry) => {
75
- const srcPath = path.join(srcDir, entry.name);
76
- const destPath = path.join(destDir, entry.name);
77
- if (entry.isDirectory()) {
78
- await copyDirRecursive(srcPath, destPath);
79
- return;
80
- }
81
- await copyFile(srcPath, destPath);
82
- }));
83
- }
84
66
  export async function bundleAgent(testSuitePaths, keep = false) {
85
67
  if (testSuitePaths.length === 0) {
86
68
  throw new Error("bundleAgent requires at least one test suite path.");
@@ -88,8 +70,9 @@ export async function bundleAgent(testSuitePaths, keep = false) {
88
70
  const projectRoot = getProjectRoot();
89
71
  const workDir = await createWorkDir(projectRoot);
90
72
  try {
91
- const agentRuntimeSrcDir = getAgentRuntimeSrcDir(projectRoot);
92
- await copyDirRecursive(agentRuntimeSrcDir, workDir);
73
+ const agentRuntimeSrcDir = getAgentRuntimeSrcDir();
74
+ logger.info(`Agent runtime found in ${agentRuntimeSrcDir}`);
75
+ await cp(agentRuntimeSrcDir, workDir, { recursive: true });
93
76
  const entrypointPath = path.join(workDir, AGENT_ENTRYPOINT_FILENAME);
94
77
  const agentSource = await readFile(entrypointPath, "utf8");
95
78
  if (!agentSource.includes(IMPORT_MARKER)) {
@@ -106,26 +89,24 @@ export async function bundleAgent(testSuitePaths, keep = false) {
106
89
  await writeFile(entrypointPath, agentSource.replace(IMPORT_MARKER, importStatements), "utf8");
107
90
  const outfilePath = path.join(workDir, AGENT_BUNDLE_FILENAME);
108
91
  const fridaCompile = getFridaCompileBin(projectRoot);
109
- let stdout;
110
- let stderr;
92
+ const args = fridaCompile.useLocal ? [entrypointPath, "-o", outfilePath] : ["frida-compile", entrypointPath, "-o", outfilePath];
111
93
  try {
112
- const args = fridaCompile.useLocal ? [entrypointPath, "-o", outfilePath] : ["frida-compile", entrypointPath, "-o", outfilePath];
113
- ({ stdout, stderr } = await execFileAsync(fridaCompile.path, args, {
94
+ execFileSync(fridaCompile.path, args, {
114
95
  cwd: projectRoot,
115
96
  shell: process.platform === "win32",
116
- }));
97
+ encoding: "utf8",
98
+ });
117
99
  }
118
100
  catch (err) {
119
- throw new Error(`frida-compile failed for entrypoint "${entrypointPath}" with suites [${testSuitePaths.join(", ")}]: ${err.message}`, { cause: err });
101
+ const stderr = err.stderr?.toString().trim();
102
+ throw new Error(`frida-compile failed for entrypoint "${entrypointPath}" with suites [${testSuitePaths.join(", ")}]: ${stderr || err.message}`, { cause: err });
120
103
  }
121
- if (stdout.trim())
122
- logger.log(`[frida-compile] ${stdout.trim()}`);
123
- if (stderr.trim())
124
- logger.warn(`[frida-compile] ${stderr.trim()}`);
104
+ logger.info(`Agent bundle sucessfully created and saved at ${outfilePath}.`);
125
105
  return await readFile(outfilePath, "utf8");
126
106
  }
127
107
  finally {
128
108
  if (!keep) {
109
+ logger.info(`Cleaning up workdir.`);
129
110
  await deleteWorkDir(workDir);
130
111
  }
131
112
  }
@@ -1,3 +1,9 @@
1
+ import chalk from "chalk";
2
+ import { writeFile } from "node:fs/promises";
3
+ import { parseArgs } from "node:util";
4
+ import { bundleAgent } from "./bundler.js";
5
+ import { collectTestSuitePaths } from "./collector.js";
6
+ import { logger } from "./logger.js";
1
7
  const usage = `
2
8
  Usage: frida-test-compiler [options] <src_path>...
3
9
 
@@ -5,34 +11,43 @@ const usage = `
5
11
  -o, --out <path> Path of the output file for JSON reporter (default: disabled)
6
12
  -h, --help Show this help message
7
13
  `;
8
- import { writeFile } from "node:fs/promises";
9
- import { bundleAgent } from "./bundler.js";
10
- import { collectTestSuitePaths } from "./collector.js";
11
- const args = process.argv.slice(2);
12
- let outPath;
13
- const srcPaths = [];
14
- for (let i = 0; i < args.length; i++) {
15
- const arg = args[i];
16
- if (arg === "-o" || arg === "--out") {
17
- outPath = args[++i];
18
- }
19
- else if (arg === "-h" || arg === "--help") {
14
+ class CliError extends Error {
15
+ }
16
+ function fail(message) {
17
+ throw new CliError(message);
18
+ }
19
+ async function main() {
20
+ const { values, positionals } = parseArgs({
21
+ allowPositionals: true,
22
+ options: {
23
+ out: { type: "string", short: "o" },
24
+ help: { type: "boolean", short: "h", default: false },
25
+ },
26
+ });
27
+ if (values.help) {
20
28
  console.log(usage);
21
- process.exit(0);
29
+ return;
30
+ }
31
+ if (positionals.length === 0) {
32
+ fail("Missing required argument <src_path>...");
33
+ }
34
+ const testSuitePaths = await collectTestSuitePaths(positionals);
35
+ const bundle = await bundleAgent(testSuitePaths);
36
+ if (values.out) {
37
+ await writeFile(values.out, bundle, "utf8");
22
38
  }
23
39
  else {
24
- srcPaths.push(arg);
40
+ console.log(bundle);
25
41
  }
26
42
  }
27
- if (srcPaths.length === 0) {
28
- console.error(usage);
29
- process.exit(1);
30
- }
31
- const testSuitePaths = await collectTestSuitePaths(srcPaths);
32
- const bundle = await bundleAgent(testSuitePaths);
33
- if (outPath) {
34
- await writeFile(outPath, bundle, "utf8");
35
- }
36
- else {
37
- console.log(bundle);
38
- }
43
+ main().catch((error) => {
44
+ if (error instanceof CliError) {
45
+ logger.error(error.message);
46
+ console.log(chalk.dim(usage));
47
+ }
48
+ else {
49
+ const msg = error instanceof Error ? error.message : String(error);
50
+ logger.error(`Fatal error: ${msg}`);
51
+ }
52
+ process.exitCode = 1;
53
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frida-test",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "license": "GPL-3.0-only",
6
6
  "homepage": "https://github.com/bernhste/frida-test/blob/main/README.md",
@@ -20,14 +20,15 @@
20
20
  },
21
21
  "bin": {
22
22
  "frida-test": "bin/frida-test.js",
23
- "frida-test-compiler": "bin/frida-test-compiler.js"
23
+ "frida-test-compile": "bin/frida-test-compile.js"
24
24
  },
25
25
  "types": "./src/agent-runtime/globals.d.ts",
26
26
  "exports": {
27
27
  ".": {
28
28
  "types": "./src/agent-runtime/globals.d.ts",
29
29
  "default": "./src/agent-runtime/globals.ts"
30
- }
30
+ },
31
+ "./protocol.js": "./src/protocol.js"
31
32
  },
32
33
  "files": [
33
34
  "dist/",
@@ -35,10 +36,11 @@
35
36
  "src/agent-runtime"
36
37
  ],
37
38
  "scripts": {
38
- "build": "npx tsc -b && npm link",
39
- "test:android": "npm run build && frida-test -U -i 'com.google.android.dialer' ./tests/",
40
- "watch": "npx tsc -b --watch",
41
- "clean": "npx tsc -b --clean && npx rimraf dist"
39
+ "build": "tsc -b",
40
+ "watch": "tsc -b --watch",
41
+ "clean": "tsc -b --clean && rimraf dist",
42
+ "test:android": "npm run build && frida-test -U -i 'com.google.android.dialer' ./tests/",
43
+ "prepublishOnly": "npm run clean && npm run build && npm run test:android"
42
44
  },
43
45
  "allowScripts": {
44
46
  "frida": true
@@ -1,5 +1,6 @@
1
1
  // AUTO-GENERATED by frida-test - do not edit
2
- import type { AgentMessage, RunSummary } from "../../src/protocol.js";
2
+ /// <reference types="frida-gum" />
3
+ import type { AgentMessage, RunSummary } from "frida-test/protocol.js";
3
4
  import "./globals.js";
4
5
  import "./matchers.js";
5
6
  import { registry, runTests } from "./registry.js";
@@ -1,4 +1,3 @@
1
- // agent/registry.ts
2
1
  import { type AgentMessage, type RunSummary, type TestError, type TestResult, type TestStatus, type TestSuiteResult } from "../../src/protocol.js";
3
2
 
4
3
  export type TestFn = () => void | Promise<void>;