Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 4 additions & 3 deletions actions/setup/js/action_conclusion_otlp.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-check
"use strict";
require("./shim.cjs");

/**
* action_conclusion_otlp.cjs
Expand Down Expand Up @@ -77,15 +78,15 @@ async function run() {
const startMs = parseJobStartMs(process.env.GITHUB_AW_OTEL_JOB_START_MS);

if (endpoints) {
console.log(`[otlp] sending conclusion span "${spanName}" to configured endpoints`);
core.info(`[otlp] sending conclusion span "${spanName}" to configured endpoints`);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 14e0979. The action_conclusion_otlp.test.cjs suite now sets up a mockCore object before module load and all console.log assertions at lines 56, 81, 87, and 119 have been updated to assert mockCore.info. All 33 tests in this file pass.

} else {
console.log("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping OTLP export (will attempt JSONL mirror)");
core.info("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping OTLP export (will attempt JSONL mirror)");
}

await sendOtlpSpan.sendJobConclusionSpan(spanName, { startMs });

if (endpoints) {
console.log("[otlp] conclusion span export attempted");
core.info("[otlp] conclusion span export attempted");
}
}

Expand Down
22 changes: 16 additions & 6 deletions actions/setup/js/action_conclusion_otlp.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ import { createRequire } from "module";
// Use CJS require so we share the same module cache as action_conclusion_otlp.cjs
const req = createRequire(import.meta.url);

// Set up global.core mock before loading the module under test so shim.cjs
// sees it already present and does not overwrite it with its stderr-based shim.
const mockCore = {
info: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
setFailed: vi.fn(),
setOutput: vi.fn(),
};
global.core = mockCore;

// Load the real send_otlp_span module and capture the original function
const sendOtlpModule = req("./send_otlp_span.cjs");
const originalSendJobConclusionSpan = sendOtlpModule.sendJobConclusionSpan;
Expand All @@ -24,7 +35,6 @@ describe("action_conclusion_otlp.cjs", () => {

beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(console, "log").mockImplementation(() => {});
mockSendJobConclusionSpan.mockResolvedValue(undefined);
// Patch the shared CJS exports object — run() accesses this at call time
sendOtlpModule.sendJobConclusionSpan = mockSendJobConclusionSpan;
Expand Down Expand Up @@ -53,7 +63,7 @@ describe("action_conclusion_otlp.cjs", () => {
it("should log that OTLP export is skipped and JSONL mirror will be attempted", async () => {
await run();

expect(console.log).toHaveBeenCalledWith("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping OTLP export (will attempt JSONL mirror)");
expect(mockCore.info).toHaveBeenCalledWith("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping OTLP export (will attempt JSONL mirror)");
});

it("should still call sendJobConclusionSpan for JSONL mirror", async () => {
Expand All @@ -78,13 +88,13 @@ describe("action_conclusion_otlp.cjs", () => {
it("should log the conclusion span export as attempted", async () => {
await run();

expect(console.log).toHaveBeenCalledWith("[otlp] conclusion span export attempted");
expect(mockCore.info).toHaveBeenCalledWith("[otlp] conclusion span export attempted");
});

it("should log the endpoint URL in the sending message", async () => {
await run();

expect(console.log).toHaveBeenCalledWith(expect.stringContaining("configured endpoints"));
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("configured endpoints"));
});

describe("span name construction", () => {
Expand Down Expand Up @@ -116,7 +126,7 @@ describe("action_conclusion_otlp.cjs", () => {

await run();

expect(console.log).toHaveBeenCalledWith('[otlp] sending conclusion span "gh-aw.setup.conclusion" to configured endpoints');
expect(mockCore.info).toHaveBeenCalledWith('[otlp] sending conclusion span "gh-aw.setup.conclusion" to configured endpoints');
});

it("should handle different job names correctly", async () => {
Expand Down Expand Up @@ -198,7 +208,7 @@ describe("action_conclusion_otlp.cjs", () => {
mockSendJobConclusionSpan.mockRejectedValueOnce(new Error("fail"));

await expect(run()).rejects.toThrow("fail");
expect(console.log).not.toHaveBeenCalledWith("[otlp] conclusion span export attempted");
expect(mockCore.info).not.toHaveBeenCalledWith("[otlp] conclusion span export attempted");
});
});
});
Expand Down
17 changes: 9 additions & 8 deletions actions/setup/js/action_setup_otlp.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-check
"use strict";
require("./shim.cjs");

/**
* action_setup_otlp.cjs
Expand Down Expand Up @@ -42,7 +43,7 @@ function writeEnvLine(filePath, key, value, logLabel, fileLabel) {
if (!filePath || !value) return;
try {
appendFileSync(filePath, `${key}=${value}\n`);
console.log(`[otlp] ${logLabel} written to ${fileLabel}`);
core.info(`[otlp] ${logLabel} written to ${fileLabel}`);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 14e0979. The action_setup_otlp.test.cjs suite now sets up a mockCore object with vi.fn() stubs before module load (so shim.cjs's guard leaves it in place), and all console.log assertions have been replaced with mockCore.info. All 48 tests in this file pass.

} catch {
/* ignore */
}
Expand Down Expand Up @@ -112,13 +113,13 @@ async function run() {
// through GitHub Actions expression evaluation is unreliable.
const inputTraceId = getActionInput("TRACE_ID").toLowerCase();
if (inputTraceId) {
console.log(`[otlp] INPUT_TRACE_ID=${inputTraceId} (will reuse activation trace)`);
core.info(`[otlp] INPUT_TRACE_ID=${inputTraceId} (will reuse activation trace)`);
} else {
console.log("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated");
core.info("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated");
}
const inputParentSpanId = getActionInput("PARENT_SPAN_ID").toLowerCase();
if (inputParentSpanId) {
console.log(`[otlp] INPUT_PARENT_SPAN_ID=${inputParentSpanId} (will parent setup span)`);
core.info(`[otlp] INPUT_PARENT_SPAN_ID=${inputParentSpanId} (will parent setup span)`);
}

// Normalize to the canonical underscore form so sendJobSetupSpan (which
Expand Down Expand Up @@ -148,9 +149,9 @@ async function run() {
}

if (!endpoints) {
console.log("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping setup span");
core.info("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping setup span");
} else {
console.log(`[otlp] sending setup span to configured endpoints`);
core.info(`[otlp] sending setup span to configured endpoints`);
}

const { traceId, spanId, parentSpanId } = await sendJobSetupSpan({
Expand All @@ -159,10 +160,10 @@ async function run() {
parentSpanId: inputParentSpanId || undefined,
});

console.log(`[otlp] resolved trace-id=${traceId}`);
core.info(`[otlp] resolved trace-id=${traceId}`);

if (endpoints) {
console.log(`[otlp] setup span sent (traceId=${traceId}, spanId=${spanId})`);
core.info(`[otlp] setup span sent (traceId=${traceId}, spanId=${spanId})`);
}

const githubOutput = process.env.GITHUB_OUTPUT;
Expand Down
38 changes: 24 additions & 14 deletions actions/setup/js/action_setup_otlp.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ import { writeFileSync, readFileSync, mkdtempSync, rmSync } from "fs";

const req = createRequire(import.meta.url);

// Set up global.core mock before loading the module under test so shim.cjs
// sees it already present and does not overwrite it with its stderr-based shim.
const mockCore = {
info: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
setFailed: vi.fn(),
setOutput: vi.fn(),
};
global.core = mockCore;

// Load send_otlp_span.cjs first so we can patch its exports before run() calls require()
// inside action_setup_otlp.cjs. Both share the same CJS module cache, so patching the
// exports object here is reflected when run() destructures from require(...send_otlp_span.cjs).
Expand Down Expand Up @@ -39,7 +50,6 @@ describe("action_setup_otlp.cjs", () => {

beforeEach(() => {
vi.clearAllMocks();
vi.spyOn(console, "log").mockImplementation(() => {});

// Patch the shared CJS module exports — run() re-destructures on every call
sendOtlpModule.sendJobSetupSpan = mockSendJobSetupSpan;
Expand Down Expand Up @@ -105,7 +115,7 @@ describe("action_setup_otlp.cjs", () => {
it("should log that OTLP export is being skipped", async () => {
await run();

expect(console.log).toHaveBeenCalledWith("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping setup span");
expect(mockCore.info).toHaveBeenCalledWith("[otlp] GH_AW_OTLP_ENDPOINTS not set, skipping setup span");
});

it("should still call sendJobSetupSpan for JSONL mirror", async () => {
Expand All @@ -117,7 +127,7 @@ describe("action_setup_otlp.cjs", () => {
it("should not log 'sending setup span to' when endpoint is absent", async () => {
await run();

const logged = vi.mocked(console.log).mock.calls.flat();
const logged = vi.mocked(mockCore.info).mock.calls.flat();
expect(logged.some(msg => typeof msg === "string" && msg.includes("sending setup span to"))).toBe(false);
});
});
Expand All @@ -130,7 +140,7 @@ describe("action_setup_otlp.cjs", () => {
it("should log sending the setup span to the configured endpoint", async () => {
await run();

expect(console.log).toHaveBeenCalledWith("[otlp] sending setup span to configured endpoints");
expect(mockCore.info).toHaveBeenCalledWith("[otlp] sending setup span to configured endpoints");
});

it("should call sendJobSetupSpan exactly once", async () => {
Expand All @@ -142,14 +152,14 @@ describe("action_setup_otlp.cjs", () => {
it("should log setup span sent with traceId and spanId", async () => {
await run();

expect(console.log).toHaveBeenCalledWith(expect.stringContaining(`traceId=${VALID_TRACE_ID}`));
expect(console.log).toHaveBeenCalledWith(expect.stringContaining(`spanId=${VALID_SPAN_ID}`));
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining(`traceId=${VALID_TRACE_ID}`));
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining(`spanId=${VALID_SPAN_ID}`));
});

it("should log the resolved trace ID", async () => {
await run();

expect(console.log).toHaveBeenCalledWith(expect.stringContaining(`trace-id=${VALID_TRACE_ID}`));
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining(`trace-id=${VALID_TRACE_ID}`));
});
});

Expand Down Expand Up @@ -271,13 +281,13 @@ describe("action_setup_otlp.cjs", () => {

await run();

expect(console.log).toHaveBeenCalledWith(expect.stringContaining("INPUT_TRACE_ID=abcdef0123456789abcdef0123456789"));
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("INPUT_TRACE_ID=abcdef0123456789abcdef0123456789"));
});

it("should log that INPUT_TRACE_ID is not set when absent", async () => {
await run();

expect(console.log).toHaveBeenCalledWith("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated");
expect(mockCore.info).toHaveBeenCalledWith("[otlp] INPUT_TRACE_ID not set, a new trace ID will be generated");
});

it("should accept the hyphen form INPUT_TRACE-ID as a fallback", async () => {
Expand Down Expand Up @@ -326,7 +336,7 @@ describe("action_setup_otlp.cjs", () => {

await run();

expect(console.log).toHaveBeenCalledWith("[otlp] INPUT_PARENT_SPAN_ID=aabbccddeeff0011 (will parent setup span)");
expect(mockCore.info).toHaveBeenCalledWith("[otlp] INPUT_PARENT_SPAN_ID=aabbccddeeff0011 (will parent setup span)");
});

it("should accept the hyphen form INPUT_PARENT-SPAN-ID as a fallback", async () => {
Expand Down Expand Up @@ -377,7 +387,7 @@ describe("action_setup_otlp.cjs", () => {
it("should log that trace-id was written to GITHUB_OUTPUT", async () => {
await run();

expect(console.log).toHaveBeenCalledWith(`[otlp] trace-id=${VALID_TRACE_ID} written to GITHUB_OUTPUT`);
expect(mockCore.info).toHaveBeenCalledWith(`[otlp] trace-id=${VALID_TRACE_ID} written to GITHUB_OUTPUT`);
});

it("should write parent-span-id to GITHUB_OUTPUT when parent span ID is valid", async () => {
Expand Down Expand Up @@ -466,19 +476,19 @@ describe("action_setup_otlp.cjs", () => {
it("should log the GITHUB_AW_OTEL_TRACE_ID write", async () => {
await run();

expect(console.log).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_TRACE_ID written to GITHUB_ENV");
expect(mockCore.info).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_TRACE_ID written to GITHUB_ENV");
});

it("should log the GITHUB_AW_OTEL_PARENT_SPAN_ID write", async () => {
await run();

expect(console.log).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_PARENT_SPAN_ID written to GITHUB_ENV");
expect(mockCore.info).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_PARENT_SPAN_ID written to GITHUB_ENV");
});

it("should log the GITHUB_AW_OTEL_JOB_START_MS write", async () => {
await run();

expect(console.log).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_JOB_START_MS written to GITHUB_ENV");
expect(mockCore.info).toHaveBeenCalledWith("[otlp] GITHUB_AW_OTEL_JOB_START_MS written to GITHUB_ENV");
});
});

Expand Down
11 changes: 6 additions & 5 deletions actions/setup/js/ai_credits_context.cjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @ts-check
require("./shim.cjs");

const fs = require("fs");
const path = require("path");
Expand Down Expand Up @@ -323,10 +324,10 @@ function parseAuditLogCombined(auditJsonlPathOverride) {
* @param {string} envVarName
*/
function logAICreditSource(label, auditValue, stdioValue, envValue, envVarName) {
if (auditValue) console.log(`[ai-credits] ${label} source=audit_log value=${auditValue}`);
else if (stdioValue) console.log(`[ai-credits] ${label} source=agent_stdio value=${stdioValue}`);
else if (envValue) console.log(`[ai-credits] ${label} source=env(${envVarName}) value=${envValue}`);
else console.log(`[ai-credits] ${label} source=none ${envVarName}=${process.env[envVarName] || "(unset)"}`);
if (auditValue) core.info(`[ai-credits] ${label} source=audit_log value=${auditValue}`);
else if (stdioValue) core.info(`[ai-credits] ${label} source=agent_stdio value=${stdioValue}`);
else if (envValue) core.info(`[ai-credits] ${label} source=env(${envVarName}) value=${envValue}`);
else core.info(`[ai-credits] ${label} source=none ${envVarName}=${process.env[envVarName] || "(unset)"}`);
}

/**
Expand Down Expand Up @@ -355,7 +356,7 @@ function resolveAICreditsFailureState({ logProvenance = true } = {}) {
: envRateLimitSignal
? "env_ignored_no_ai_credits"
: "none";
console.log(`[ai-credits] rateLimitSignal source=${rawRateLimitSignalSource}`);
core.info(`[ai-credits] rateLimitSignal source=${rawRateLimitSignalSource}`);
}

const aiCredits = auditAICredits || stdioSignals.aiCredits || envAICredits || "";
Expand Down
9 changes: 5 additions & 4 deletions actions/setup/js/emit_outcome_spans.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-check
/// <reference types="@actions/github-script" />
require("./shim.cjs");

/**
* emit_outcome_spans.cjs
Expand Down Expand Up @@ -76,15 +77,15 @@ async function main() {
const awInfo = readJSONIfExists(AW_INFO_PATH) || {};

if (evaluations.length === 0 && (!summary || summary.total_outcomes === 0)) {
console.log("[outcome-otel] No outcome evaluations to export");
core.info("[outcome-otel] No outcome evaluations to export");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 14e0979. The emit_outcome_spans.test.cjs suite now sets up a mockCore object before module load and all three console.log assertions (lines 171, 326, 359) have been updated to assert mockCore.info. All 3 tests in this file pass.

return;
}

const endpoints = parseOTLPEndpoints();
const hasEndpoints = endpoints.length > 0;

if (!hasEndpoints) {
console.log("[outcome-otel] No OTLP endpoints configured, writing JSONL mirror only");
core.info("[outcome-otel] No OTLP endpoints configured, writing JSONL mirror only");
}

// Read aw_info.json first: GH_AW_INFO_* env vars are only present during setup,
Expand Down Expand Up @@ -304,11 +305,11 @@ async function main() {
// Always write to local JSONL mirror
appendToOTLPJSONL(payload);

console.log(`[outcome-otel] Emitting ${evaluations.length} outcome span(s) + 1 summary span`);
core.info(`[outcome-otel] Emitting ${evaluations.length} outcome span(s) + 1 summary span`);

if (hasEndpoints) {
await sendOTLPToAllEndpoints(endpoints, payload, { skipJSONL: true });
console.log(`[outcome-otel] Exported to ${endpoints.length} endpoint(s)`);
core.info(`[outcome-otel] Exported to ${endpoints.length} endpoint(s)`);
}
}

Expand Down
Loading
Loading