Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for CodeChecker warning/error reasons #146

Merged
merged 3 commits into from
Jul 15, 2024
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
14 changes: 7 additions & 7 deletions src/backend/executor/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
parseShellArgsAndReplaceVariables,
replaceVariables
} from '../../utils/config';
import { ProcessStatus, ProcessType, ScheduledProcess } from '.';
import { ProcessStatusType, ProcessType, ScheduledProcess } from '.';
import { NotificationType } from '../../editor/notifications';
import { Editor } from '../../editor';

Expand Down Expand Up @@ -461,7 +461,7 @@ export class ExecutorBridge implements Disposable {
process.processStdout((output) => processOutput += output);

process.processStatusChange((status) => {
if (status === ProcessStatus.finished) {
if (status.type === ProcessStatusType.finished) {
ExtensionApi.metadata.parseCheckerData(processOutput);
}
});
Expand Down Expand Up @@ -504,7 +504,7 @@ export class ExecutorBridge implements Disposable {
process.processStdout((output) => processOutput += output);

process.processStatusChange((status) => {
if (status === ProcessStatus.finished) {
if (status.type === ProcessStatusType.finished) {
ExtensionApi.diagnostics.parseDiagnosticsData(processOutput);
}
});
Expand Down Expand Up @@ -558,9 +558,9 @@ export class ExecutorBridge implements Disposable {
process.processStdout((output) => processOutput += output);

process.processStatusChange(async (status) => {
switch (status) {
case ProcessStatus.running: return;
case ProcessStatus.finished:
switch (status.type) {
case ProcessStatusType.running: return;
case ProcessStatusType.finished:
try {
const data = JSON.parse(processOutput) as AnalyzerVersion;

Expand Down Expand Up @@ -651,7 +651,7 @@ export class ExecutorBridge implements Disposable {
}

break;
case ProcessStatus.removed:
case ProcessStatusType.removed:
if (this.checkedVersion === undefined) {
this.checkedVersion = false;
}
Expand Down
118 changes: 82 additions & 36 deletions src/backend/executor/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ import * as os from 'os';
import { quote } from 'shell-quote';
import { Disposable, Event, EventEmitter, ExtensionContext, workspace } from 'vscode';

export enum ProcessStatus {
export enum ProcessStatusType {
notRunning,
running,
// When added to the execution queue
queued,
killed,
finished,
warning,
errored,
// When overwritten in the queue with 'replace', or cleared
removed,
}

export interface ProcessStatus {
type: ProcessStatusType,
reason?: string
}

export enum ProcessType {
analyze = 'CodeChecker analyze',
checkers = 'CodeChecker checkers',
Expand Down Expand Up @@ -57,6 +63,7 @@ export class ScheduledProcess implements Disposable {
public readonly commandArgs: string[];

private activeProcess?: childProcess.ChildProcess;
private lastLogMessage?: string;

/** Contains parameters for the executor. All members are defined. */
public readonly processParameters: ProcessParameters;
Expand Down Expand Up @@ -85,8 +92,8 @@ export class ScheduledProcess implements Disposable {
return this._processStderr.event;
}

private _processStatus: ProcessStatus = ProcessStatus.notRunning;
public get processStatus(): ProcessStatus {
private _processStatus: ProcessStatusType = ProcessStatusType.notRunning;
public get processStatus(): ProcessStatusType {
return this._processStatus;
}

Expand All @@ -106,14 +113,27 @@ export class ScheduledProcess implements Disposable {
if (this.processParameters.forwardStdoutToLogs === undefined) {
this.processParameters.forwardStdoutToLogs = !forwardDefaults.includes(processType);
}

const parseLogMessage = (stdout: string) => {
// Do not store json output or meta messages as last error
const lines = stdout.split('\n')
.filter((line) => !line.startsWith('{') && !line.startsWith('>') && line !== '');
Discookie marked this conversation as resolved.
Show resolved Hide resolved

if (lines.length > 0) {
this.lastLogMessage = lines[lines.length - 1];
}
};

this.processStdout(parseLogMessage, this);
this.processStderr(parseLogMessage, this);
}

dispose() {
if (this.activeProcess) {
this.killProcess();
}

this._processStatusChange.fire(ProcessStatus.removed);
this._processStatusChange.fire({ type: ProcessStatusType.removed });

this._processStatusChange.dispose();
this._processStdout.dispose();
Expand Down Expand Up @@ -153,7 +173,7 @@ export class ScheduledProcess implements Disposable {

this.activeProcess.on('error', (err) => {
this._processStderr.fire(`>>> Process '${commonName}' errored: ${err.message}\n`);
this.updateStatus(ProcessStatus.errored);
this.updateStatus(ProcessStatusType.errored);
});
// Guaranteed to fire after all datastreams are closed
this.activeProcess.on('close', (code: number | null) => {
Expand All @@ -163,15 +183,15 @@ export class ScheduledProcess implements Disposable {
case null:
case 0:
case 2:
this.updateStatus(ProcessStatus.finished);
this.updateStatus(ProcessStatusType.finished);
break;
default:
this.updateStatus(ProcessStatus.errored);
this.updateStatus(ProcessStatusType.errored);
break;
}
});

this.updateStatus(ProcessStatus.running);
this.updateStatus(ProcessStatusType.running);
}

public killProcess() {
Expand All @@ -182,28 +202,54 @@ export class ScheduledProcess implements Disposable {
this.activeProcess.kill('SIGINT');
this._processStderr.fire('>>> Process killed\n');

this.updateStatus(ProcessStatus.killed);
this.updateStatus(ProcessStatusType.killed);
}

private updateStatus(status: ProcessStatus) {
switch (status) {
case ProcessStatus.running:
if (this._processStatus !== ProcessStatus.running) {
this._processStatus = ProcessStatus.running;
this._processStatusChange.fire(ProcessStatus.running);
private updateStatus(type: ProcessStatusType) {
switch (type) {
case ProcessStatusType.running:
if (this._processStatus !== ProcessStatusType.running) {
this._processStatus = ProcessStatusType.running;
this._processStatusChange.fire({ type: ProcessStatusType.running });
}
break;
case ProcessStatus.removed:
return;
case ProcessStatusType.removed:
// dispose() calls killProcess before dispatching this event.
this._processStatusChange.fire(ProcessStatus.removed);
break;
default:
if (this._processStatus === ProcessStatus.running) {
this.activeProcess = undefined;
this._processStatus = ProcessStatus.notRunning;
this._processStatusChange.fire(status);
this._processStatusChange.fire({ type: ProcessStatusType.removed });
return;
}

if (this._processStatus === ProcessStatusType.running) {
this.activeProcess = undefined;
this._processStatus = ProcessStatusType.notRunning;

const lastLogMessage = this.lastLogMessage?.replace(/^\[.+\]/, '');
const lastLogSeverity = this.lastLogMessage?.match(/^\[(\w+)/)?.[1];

if (type === ProcessStatusType.errored) {
this._processStatusChange.fire({ type, reason: lastLogMessage });
return;
} else if (type !== ProcessStatusType.finished) {
this._processStatusChange.fire({ type });
return;
}

// Refine the finished process status based on the last log message
switch (lastLogSeverity) {
case 'CRITICAL':
case 'ERROR':
this._processStatusChange.fire({ type: ProcessStatusType.errored, reason: lastLogMessage });
break;
case 'WARNING':
this._processStatusChange.fire({ type: ProcessStatusType.warning, reason: lastLogMessage });
break;
case 'DEBUG':
this._processStatusChange.fire({ type: ProcessStatusType.finished });
break;
default:
this._processStatusChange.fire({ type: ProcessStatusType.finished, reason: lastLogMessage });
break;
}
break;
}
}
}
Expand Down Expand Up @@ -253,8 +299,8 @@ export class ExecutorManager implements Disposable {
return this._processStderr.event;
}

private _processStatus: ProcessStatus = ProcessStatus.notRunning;
public get processStatus(): ProcessStatus {
private _processStatus: ProcessStatusType = ProcessStatusType.notRunning;
public get processStatus(): ProcessStatusType {
return this._processStatus;
}

Expand All @@ -273,12 +319,12 @@ export class ExecutorManager implements Disposable {
}

private updateStatus([status, process]: [ProcessStatus, ScheduledProcess]) {
switch (status) {
case ProcessStatus.removed:
switch (status.type) {
case ProcessStatusType.removed:
this._processStatusChange.fire([status, process]);
break;
case ProcessStatus.running:
case ProcessStatus.queued:
case ProcessStatusType.running:
case ProcessStatusType.queued:
this._processStatusChange.fire([status, process]);
break;
default:
Expand Down Expand Up @@ -330,7 +376,7 @@ export class ExecutorManager implements Disposable {
switch (method) {
case 'replace':
for (const entry of namedQueue) {
this.updateStatus([ProcessStatus.removed, entry]);
this.updateStatus([{ type: ProcessStatusType.removed }, entry]);
entry.dispose();
}

Expand All @@ -345,7 +391,7 @@ export class ExecutorManager implements Disposable {
break;
}

this.updateStatus([ProcessStatus.queued, process]);
this.updateStatus([{ type: ProcessStatusType.queued }, process]);
this.queue.set(name, namedQueue);

this.startNextProcess();
Expand All @@ -359,7 +405,7 @@ export class ExecutorManager implements Disposable {
if (namedQueue.some((queueItem) => queueItem.commandLine === process.commandLine)) {
if (!silent) {
for (const entry of namedQueue.filter((queueItem) => queueItem.commandLine === process.commandLine)) {
this.updateStatus([ProcessStatus.removed, entry]);
this.updateStatus([{ type: ProcessStatusType.removed }, entry]);
entry.dispose();
}
}
Expand All @@ -375,15 +421,15 @@ export class ExecutorManager implements Disposable {
public clearQueue(name?: string) {
if (name) {
for (const entry of this.queue.get(name) ?? []) {
this.updateStatus([ProcessStatus.removed, entry]);
this.updateStatus([{ type: ProcessStatusType.removed }, entry]);
entry.dispose();
}

this.queue.set(name, []);
} else {
for (const [, queue] of this.queue.entries()) {
for (const entry of queue) {
this.updateStatus([ProcessStatus.removed, entry]);
this.updateStatus([{ type: ProcessStatusType.removed }, entry]);
entry.dispose();
}
}
Expand Down
18 changes: 10 additions & 8 deletions src/editor/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from 'vscode';
import { Editor } from '.';
import { ExtensionApi } from '../backend';
import { ProcessStatus, ScheduledProcess } from '../backend/executor/process';
import { ProcessStatus, ProcessStatusType, ScheduledProcess } from '../backend/executor/process';
import { getConfigAndReplaceVariables } from '../utils/config';
import { NotificationType } from './notifications';

Expand Down Expand Up @@ -93,28 +93,30 @@ export class ExecutorAlerts {
return;
}

if (status === ProcessStatus.running) {
if (status.type === ProcessStatusType.running) {
this.statusBarItem.text = '$(loading) CodeChecker: analysis in progress...';
this.statusBarItem.show();
return;
}

switch (status) {
case ProcessStatus.finished:
switch (status.type) {
case ProcessStatusType.finished:
this.statusBarItem.text = '$(testing-passed-icon) CodeChecker: analysis finished';
break;
case ProcessStatus.killed:
case ProcessStatusType.killed:
this.statusBarItem.text = '$(testing-failed-icon) CodeChecker: analysis killed';
break;
case ProcessStatus.notRunning:
case ProcessStatusType.notRunning:
this.statusBarItem.text = '$(info) CodeChecker: ready';
break;
case ProcessStatus.errored:
case ProcessStatusType.errored:
this.statusBarItem.text = '$(testing-error-icon) CodeChecker: analysis errored';

const logLocation = status.reason ? 'sidebar' : 'output log';

Editor.notificationHandler.showNotification(
NotificationType.error,
'CodeChecker finished with error - see logs for details'
`CodeChecker finished with error - see the ${logLocation} for details`
);
break;
default:
Expand Down
Loading
Loading