Skip to content

Commit

Permalink
feat: test all APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
kristof-mattei committed Sep 16, 2023
1 parent 45310b9 commit a992ca0
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 118 deletions.
121 changes: 121 additions & 0 deletions src/clippy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import * as core from "@actions/core";
import * as exec from "@actions/exec";
import { Cargo, Cross } from "@actions-rs-plus/core";

import type * as input from "./input";
import { OutputParser } from "./outputParser";
import { Reporter } from "./reporter";
import type { AnnotationWithMessageAndLevel, Context, Stats } from "./schema";

type Program = Cargo | Cross;

interface ClippyResult {
stats: Stats;
annotations: AnnotationWithMessageAndLevel[];
exitCode: number;
}

async function buildContext(program: Program): Promise<Context> {
const context: Context = {
cargo: "",
clippy: "",
rustc: "",
};

await Promise.all([
await exec.exec("rustc", ["-V"], {
silent: true,
listeners: {
stdout: (buffer: Buffer) => {
return (context.rustc = buffer.toString().trim());
},
},
}),
await program.call(["-V"], {
silent: true,
listeners: {
stdout: (buffer: Buffer) => {
return (context.cargo = buffer.toString().trim());
},
},
}),
await program.call(["clippy", "-V"], {
silent: true,
listeners: {
stdout: (buffer: Buffer) => {
return (context.clippy = buffer.toString().trim());
},
},
}),
]);

return context;
}

async function runClippy(actionInput: input.ParsedInput, program: Program): Promise<ClippyResult> {
const args = buildArgs(actionInput);
const outputParser = new OutputParser();

let exitCode = 0;

try {
core.startGroup("Executing cargo clippy (JSON output)");
exitCode = await program.call(args, {
ignoreReturnCode: true,
failOnStdErr: false,
listeners: {
stdline: (line: string) => {
outputParser.tryParseClippyLine(line);
},
},
});
} finally {
core.endGroup();
}

return {
stats: outputParser.stats,
annotations: outputParser.annotations,
exitCode,
};
}

function getProgram(useCross: boolean): Promise<Program> {
if (useCross) {
return Cross.getOrInstall();
} else {
return Cargo.get();
}
}

export async function run(actionInput: input.ParsedInput): Promise<void> {
const program: Program = await getProgram(actionInput.useCross);

const context = await buildContext(program);

const { stats, annotations, exitCode } = await runClippy(actionInput, program);

await new Reporter().report(stats, annotations, context);

if (exitCode !== 0) {
throw new Error(`Clippy had exited with the ${exitCode} exit code`);
}
}

function buildArgs(actionInput: input.ParsedInput): string[] {
const args: string[] = [];

// Toolchain selection MUST go first in any condition
if (actionInput.toolchain) {
args.push(`+${actionInput.toolchain}`);
}

args.push("clippy");

// `--message-format=json` should just right after the `cargo clippy`
// because usually people are adding the `-- -D warnings` at the end
// of arguments and it will mess up the output.
args.push("--message-format=json");

return args.concat(actionInput.args);
}
118 changes: 1 addition & 117 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,8 @@
import * as core from "@actions/core";
import * as exec from "@actions/exec";
import { Cargo, Cross } from "@actions-rs-plus/core";

import * as input from "./input";
import { OutputParser } from "./outputParser";
import { Reporter } from "./reporter";
import type { AnnotationWithMessageAndLevel, Context, Stats } from "./schema";

type Program = Cargo | Cross;

interface ClippyResult {
stats: Stats;
annotations: AnnotationWithMessageAndLevel[];
exitCode: number;
}

async function buildContext(program: Program): Promise<Context> {
const context: Context = {
cargo: "",
clippy: "",
rustc: "",
};

await Promise.all([
await exec.exec("rustc", ["-V"], {
silent: true,
listeners: {
stdout: (buffer: Buffer) => {
return (context.rustc = buffer.toString().trim());
},
},
}),
await program.call(["-V"], {
silent: true,
listeners: {
stdout: (buffer: Buffer) => {
return (context.cargo = buffer.toString().trim());
},
},
}),
await program.call(["clippy", "-V"], {
silent: true,
listeners: {
stdout: (buffer: Buffer) => {
return (context.clippy = buffer.toString().trim());
},
},
}),
]);

return context;
}

async function runClippy(actionInput: input.ParsedInput, program: Program): Promise<ClippyResult> {
const args = buildArgs(actionInput);
const outputParser = new OutputParser();

let exitCode = 0;

try {
core.startGroup("Executing cargo clippy (JSON output)");
exitCode = await program.call(args, {
ignoreReturnCode: true,
failOnStdErr: false,
listeners: {
stdline: (line: string) => {
outputParser.tryParseClippyLine(line);
},
},
});
} finally {
core.endGroup();
}

return {
stats: outputParser.stats,
annotations: outputParser.annotations,
exitCode,
};
}

function getProgram(useCross: boolean): Promise<Program> {
if (useCross) {
return Cross.getOrInstall();
} else {
return Cargo.get();
}
}

export async function run(actionInput: input.ParsedInput): Promise<void> {
const program: Program = await getProgram(actionInput.useCross);

const context = await buildContext(program);

const { stats, annotations, exitCode } = await runClippy(actionInput, program);

await new Reporter().report(stats, annotations, context);

if (exitCode !== 0) {
throw new Error(`Clippy had exited with the ${exitCode} exit code`);
}
}
import { run } from "clippy";

async function main(): Promise<void> {
try {
Expand All @@ -117,22 +19,4 @@ async function main(): Promise<void> {
}
}

function buildArgs(actionInput: input.ParsedInput): string[] {
const args: string[] = [];

// Toolchain selection MUST go first in any condition
if (actionInput.toolchain) {
args.push(`+${actionInput.toolchain}`);
}

args.push("clippy");

// `--message-format=json` should just right after the `cargo clippy`
// because usually people are adding the `-- -D warnings` at the end
// of arguments and it will mess up the output.
args.push("--message-format=json");

return args.concat(actionInput.args);
}

void main();
2 changes: 1 addition & 1 deletion src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface MaybeCargoMessage {
}

export interface CargoMessage {
reason: string;
reason: "compiler-message";
message: {
code: string;
level: string;
Expand Down
122 changes: 122 additions & 0 deletions src/tests/clippy.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import * as exec from "@actions/exec";

import { run } from "clippy";
import { type ParsedInput } from "input";
import { Reporter } from "reporter";
import { type CargoMessage } from "schema";

jest.mock("@actions/core");
jest.mock("@actions/exec");
jest.mock("reporter");

describe("clippy", () => {
it("runs with cargo", async () => {
jest.spyOn(exec, "exec").mockResolvedValue(0);

const actionInput: ParsedInput = {
toolchain: "stable",
args: [],
useCross: false,
};

await expect(run(actionInput)).resolves.toBeUndefined();
});

it("runs with cross", async () => {
jest.spyOn(exec, "exec").mockResolvedValue(0);

const actionInput: ParsedInput = {
toolchain: "stable",
args: [],
useCross: true,
};

await expect(run(actionInput)).resolves.toBeUndefined();
});

it("reports when clippy fails", async () => {
jest.spyOn(exec, "exec").mockImplementation((_commandline: string, args?: string[] | undefined) => {
const expected = ["clippy", "--message-format=json"];

if (
(args ?? []).length > 0 &&
expected.every((c) => {
return args?.includes(c);
})
) {
return Promise.resolve(101);
} else {
return Promise.resolve(0);
}
});

const actionInput: ParsedInput = {
toolchain: "stable",
args: [],
useCross: false,
};

await expect(run(actionInput)).rejects.toThrow(/Clippy had exited with the (\d)+ exit code/);
});

it("records versions", async () => {
const reportSpy = jest.spyOn(Reporter.prototype, "report");
jest.spyOn(exec, "exec").mockImplementation((commandline: string, args?: string[], options?: exec.ExecOptions) => {
if (commandline.endsWith("cargo")) {
if (args?.[0] === "-V") {
options?.listeners?.stdout?.(Buffer.from("cargo version"));
} else if (args?.[0] === "clippy" && args?.[1] === "-V") {
options?.listeners?.stdout?.(Buffer.from("clippy version"));
}
} else if (commandline === "rustc" && args?.[0] === "-V") {
options?.listeners?.stdout?.(Buffer.from("rustc version"));
}
return Promise.resolve(0);
});

const actionInput: ParsedInput = {
toolchain: "stable",
args: [],
useCross: false,
};

await expect(run(actionInput)).resolves.toBeUndefined();

expect(reportSpy).toBeCalledWith({ error: 0, help: 0, ice: 0, note: 0, warning: 0 }, [], { cargo: "cargo version", clippy: "clippy version", rustc: "rustc version" });
});

it("clippy captures stdout", async () => {
jest.spyOn(exec, "exec").mockImplementation((_commandline: string, args?: string[] | undefined, options?: exec.ExecOptions) => {
const expected = ["clippy", "--message-format=json"];

if (
(args ?? []).length > 0 &&
expected.every((c) => {
return args?.includes(c);
})
) {
const data: CargoMessage = {
reason: "compiler-message",
message: {
code: "500",
level: "warning",
message: "message",
rendered: "rendered",
spans: [{ is_primary: true, file_name: "main.rs", line_start: 12, line_end: 12, column_start: 30, column_end: 45 }],
},
};
options?.listeners?.stdline?.(JSON.stringify(data));
}

return Promise.resolve(0);
});

const actionInput: ParsedInput = {
toolchain: "stable",
args: [],
useCross: false,
};

await expect(run(actionInput)).resolves.toBeUndefined();
});
});
Loading

0 comments on commit a992ca0

Please sign in to comment.