Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
6 changes: 6 additions & 0 deletions scripts/bin-test/sources/broken-syntax/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const functions = require("firebase-functions");

// This will cause a syntax error
exports.broken = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!"
}); // Missing closing parenthesis
81 changes: 81 additions & 0 deletions scripts/bin-test/sources/broken-syntax/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions scripts/bin-test/sources/broken-syntax/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "broken-syntax",
"main": "index.js",
"dependencies": {
"firebase-functions": "file:../../../.."
}
}
200 changes: 128 additions & 72 deletions scripts/bin-test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,13 @@
interface Testcase {
name: string;
modulePath: string;
expected: Record<string, any>;
expected: Record<string, unknown>;
}

interface DiscoveryResult {
success: boolean;
manifest?: Record<string, unknown>;
error?: string;
}

async function retryUntil(
Expand Down Expand Up @@ -134,102 +140,107 @@
await Promise.race([retry, timedOut]);
}

async function startBin(
tc: Testcase,
debug?: boolean
): Promise<{ port: number; cleanup: () => Promise<void> }> {

Check failure on line 143 in scripts/bin-test/test.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Delete `⏎`
async function runHttpDiscovery(modulePath: string): Promise<DiscoveryResult> {
const getPort = promisify(portfinder.getPort) as () => Promise<number>;
const port = await getPort();

const proc = subprocess.spawn("npx", ["firebase-functions"], {
cwd: path.resolve(tc.modulePath),
cwd: path.resolve(modulePath),
env: {
PATH: process.env.PATH,
GLCOUD_PROJECT: "test-project",
GCLOUD_PROJECT: "test-project",
PORT: port.toString(),
FUNCTIONS_CONTROL_API: "true",
},
});
if (!proc) {
throw new Error("Failed to start firebase functions");
}
proc.stdout?.on("data", (chunk: Buffer) => {
console.log(chunk.toString("utf8"));
});
proc.stderr?.on("data", (chunk: Buffer) => {
console.log(chunk.toString("utf8"));
});

await retryUntil(async () => {
try {
await fetch(`http://localhost:${port}/__/functions.yaml`);
} catch (e) {
if (e?.code === "ECONNREFUSED") {
return false;
try {
// Wait for server to be ready
await retryUntil(async () => {
try {
await fetch(`http://localhost:${port}/__/functions.yaml`);
return true;
} catch (e: unknown) {
const error = e as { code?: string };
return error.code !== "ECONNREFUSED";
}
throw e;
}, TIMEOUT_L);

const res = await fetch(`http://localhost:${port}/__/functions.yaml`);
const body = await res.text();

Check failure on line 172 in scripts/bin-test/test.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Delete `····`
if (res.status === 200) {
const manifest = yaml.load(body) as Record<string, unknown>;
return { success: true, manifest };
} else {
return { success: false, error: body };
}
return true;
}, TIMEOUT_L);
} finally {
proc.kill(9);
}
}

if (debug) {
proc.stdout?.on("data", (data: unknown) => {
console.log(`[${tc.name} stdout] ${data}`);
async function runStdioDiscovery(modulePath: string): Promise<DiscoveryResult> {
return new Promise((resolve, reject) => {
const proc = subprocess.spawn("npx", ["firebase-functions"], {
cwd: path.resolve(modulePath),
env: {
PATH: process.env.PATH,
GCLOUD_PROJECT: "test-project",
FUNCTIONS_CONTROL_API: "true",
FUNCTIONS_DISCOVERY_MODE: "stdio",
},
});

proc.stderr?.on("data", (data: unknown) => {
console.log(`[${tc.name} stderr] ${data}`);
let stderr = "";

proc.stderr?.on("data", (chunk: Buffer) => {
stderr += chunk.toString("utf8");
});
}

return {
port,
cleanup: async () => {
process.kill(proc.pid, 9);
await retryUntil(async () => {
try {
process.kill(proc.pid, 0);
} catch {
// process.kill w/ signal 0 will throw an error if the pid no longer exists.
return Promise.resolve(true);
}
return Promise.resolve(false);
}, TIMEOUT_L);
},
};
proc.on("close", () => {
// Try to parse manifest
const manifestMatch = stderr.match(/__FIREBASE_FUNCTIONS_MANIFEST__:(.+)/);
if (manifestMatch) {
const base64 = manifestMatch[1];
const manifestJson = Buffer.from(base64, "base64").toString("utf8");
const manifest = JSON.parse(manifestJson) as Record<string, unknown>;
resolve({ success: true, manifest });
return;
}

Check failure on line 212 in scripts/bin-test/test.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Delete `······`
// Try to parse error
const errorMatch = stderr.match(/__FIREBASE_FUNCTIONS_MANIFEST_ERROR__:(.+)/);
if (errorMatch) {
resolve({ success: false, error: errorMatch[1] });
return;
}

Check failure on line 219 in scripts/bin-test/test.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Delete `······`
resolve({ success: false, error: "No manifest or error found" });
});

proc.on("error", (err) => {
reject(err);
});
});
}

describe("functions.yaml", function () {
// eslint-disable-next-line @typescript-eslint/no-invalid-this
this.timeout(TIMEOUT_XL);

function runTests(tc: Testcase) {
let port: number;
let cleanup: () => Promise<void>;

before(async () => {
const r = await startBin(tc);
port = r.port;
cleanup = r.cleanup;
});

after(async () => {
await cleanup?.();
});

it("functions.yaml returns expected Manifest", async function () {
function runDiscoveryTests(
tc: Testcase,

Check failure on line 234 in scripts/bin-test/test.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Delete `·`
discoveryFn: (path: string) => Promise<DiscoveryResult>
) {
it("returns expected manifest", async function () {
// eslint-disable-next-line @typescript-eslint/no-invalid-this
this.timeout(TIMEOUT_M);

const res = await fetch(`http://localhost:${port}/__/functions.yaml`);
const text = await res.text();
let parsed: any;
try {
parsed = yaml.load(text);
} catch (err) {
throw new Error(`Failed to parse functions.yaml: ${err}`);
}
expect(parsed).to.be.deep.equal(tc.expected);
const result = await discoveryFn(tc.modulePath);
expect(result.success).to.be.true;
expect(result.manifest).to.deep.equal(tc.expected);
});
}

Expand Down Expand Up @@ -318,9 +329,18 @@
},
];

const discoveryMethods = [
{ name: "http", fn: runHttpDiscovery },
{ name: "stdio", fn: runStdioDiscovery },
];

for (const tc of testcases) {
describe(tc.name, () => {
runTests(tc);
for (const discovery of discoveryMethods) {
describe(`${discovery.name} discovery`, () => {
runDiscoveryTests(tc, discovery.fn);
});
}
});
}
});
Expand Down Expand Up @@ -348,9 +368,45 @@
},
];

const discoveryMethods = [
{ name: "http", fn: runHttpDiscovery },
{ name: "stdio", fn: runStdioDiscovery },
];

for (const tc of testcases) {
describe(tc.name, () => {
runTests(tc);
for (const discovery of discoveryMethods) {
describe(`${discovery.name} discovery`, () => {
runDiscoveryTests(tc, discovery.fn);
});
}
});
}
});

Check failure on line 386 in scripts/bin-test/test.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Delete `··`
describe("error handling", function () {

Check failure on line 387 in scripts/bin-test/test.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Unexpected function expression
const errorTestcases = [
{
name: "broken syntax",
modulePath: "./scripts/bin-test/sources/broken-syntax",
expectedError: "missing ) after argument list",
},
];

const discoveryMethods = [
{ name: "http", fn: runHttpDiscovery },
{ name: "stdio", fn: runStdioDiscovery },
];

for (const tc of errorTestcases) {
describe(tc.name, () => {
for (const discovery of discoveryMethods) {
it(`${discovery.name} discovery handles error correctly`, async function () {

Check failure on line 404 in scripts/bin-test/test.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Unexpected function expression
const result = await discovery.fn(tc.modulePath);
expect(result.success).to.be.false;
expect(result.error).to.include(tc.expectedError);
});
}
});
}
});
Expand Down
Loading
Loading