Skip to content
Snippets Groups Projects
Commit 08b6ebf8 authored by chrigiBisig's avatar chrigiBisig
Browse files

create classes that are required to run and detect tests

parent 56a93685
Branches
Tags
2 merge requests!2Feature/linting,!1Feature/direct test detection
Showing
with 349 additions and 1 deletion
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import * as fsp from "fs/promises";
import { TestElement } from "./TestElement";
import { TestSuite } from "./TestSuite";
import { ResultAnalyzer } from '../ResultAnalyzer/ResultAnalyzer';
import { TestRunner } from '../TestRunner/TestRunner';
export class CUTETest implements TestElement {
private currentRun: { testRun: vscode.TestRun; duration: number; } | undefined;
private readonly workingDirectory: string;
public testSuite : TestSuite | undefined;
constructor(
public readonly identifier: string,
public readonly lable: string,
public readonly file: string,
public readonly line: string | undefined,
public readonly execPath: vscode.Uri,
public readonly testItem: vscode.TestItem
) {
this.workingDirectory = path.dirname(execPath.fsPath);
}
getLabel():string {
return this.identifier + `_` + this.lable;
}
async run(testRun: vscode.TestRun, testRunner: TestRunner, resultAnalyzer: ResultAnalyzer): Promise<void> {
if(this.currentRun){
//this test is already running
return;
}
this.currentRun = { testRun, duration: 0 };
try{
const result = await testRunner.runTest(this, this.currentRun.testRun.name || "CUTE_TEST");
this.currentRun.duration = result.runDuration;
await resultAnalyzer.analyze(result.result, this._onTestSuccess.bind(this), this._onTestFailure.bind(this), this._onTestError.bind(this));
}
catch(ex){
this._onTestError(this.identifier, "Oops something went wrong while running testcase");
}
}
async debug(testRun: vscode.TestRun, testRunner: TestRunner, resultAnalyzer: ResultAnalyzer): Promise<void> {
if(this.currentRun){
//this test is already running
return;
}
this.currentRun = { testRun, duration: 0 };
const workspace = vscode.workspace.getWorkspaceFolder(vscode.Uri.parse(this.file));
const debugConfig = this.getDebugConfig();
const started = new Promise<void>(resolve => {
vscode.debug.onDidStartDebugSession((session: vscode.DebugSession) => {
resolve();
});
});
const terminated = new Promise<void>(resolve => {
vscode.debug.onDidTerminateDebugSession((session: vscode.DebugSession) => {
resolve();
});
});
const debugSessionStarted = await vscode.debug.startDebugging(workspace, debugConfig);
if(debugSessionStarted){
await started;
const start = Date.now();
await terminated;
const testExecutableName = path.basename(this.execPath.path);
const resultPath = path.join(this.workingDirectory , `${testExecutableName}.xml`);
this.currentRun.duration = Date.now() - start;
await resultAnalyzer.analyze(resultPath, this._onTestSuccess.bind(this), this._onTestFailure.bind(this), this._onTestError.bind(this));
}
}
private _onTestSuccess(testId: string) : void{
this.currentRun?.testRun.passed(this.testItem, this.currentRun?.duration);
this.currentRun = undefined;
}
private _onTestError(testId: string, errorMessage: string) :void{
const message = new vscode.TestMessage(errorMessage);
this.currentRun?.testRun.errored(this.testItem, message, this.currentRun?.duration);
this.currentRun = undefined;
}
private _onTestFailure(testId: string, errorMessage: string, line: number) : void{
const message = new vscode.TestMessage(errorMessage);
const range = new vscode.Range(line-1, 0, line, 0);
message.location = new vscode.Location(vscode.Uri.file(this.file), range);
this.currentRun?.testRun.failed(this.testItem, message, this.currentRun?.duration);
this.currentRun = undefined;
}
private _hasExtension(id: string): boolean {
return vscode.extensions.all.find(e => e.id === id) !== undefined;
}
private getDebugConfig () : vscode.DebugConfiguration {
const envVars = Object.assign({}, process.env);
const template: vscode.DebugConfiguration = {
name: '${label} (${suiteLabel})',
request: 'launch',
type: 'cppdbg',
};
if (this._hasExtension('vadimcn.vscode-lldb')) {
Object.assign(template, {
type: 'cppdbg',
MIMode: 'lldb',
program: this.execPath.fsPath,
args: this.getExecArgs(),
cwd: this.workingDirectory,
env: envVars,
sourceMap: [],
});
} else if (this._hasExtension('webfreak.debug')) {
Object.assign(template, {
type: 'gdb',
target: this.execPath.fsPath,
arguments: this.getExecArgs().join(','),
cwd: this.workingDirectory,
env: envVars,
valuesFormatting: 'prettyPrinters',
pathSubstitutions: [],
});
if (platformUtil.is('darwin')) {
template.type = 'lldb-mi';
// Note: for LLDB you need to have lldb-mi in your PATH
// If you are on OS X you can add lldb-mi to your path using ln -s /Applications/Xcode.app/Contents/Developer/usr/bin/lldb-mi /usr/local/bin/lldb-mi if you have Xcode.
template.lldbmipath = '/Applications/Xcode.app/Contents/Developer/usr/bin/lldb-mi';
}
} else if (this._hasExtension('ms-vscode.cpptools')) {
Object.assign(template, {
type: 'cppvsdbg',
linux: { type: 'cppdbg', MIMode: 'gdb' },
darwin: { type: 'cppdbg', MIMode: 'lldb' },
windows: { type: 'cppvsdbg' },
program: this.execPath.fsPath,
args: this.getExecArgs(),
cwd: this.workingDirectory,
env: envVars,
environment: Object.keys(envVars).map(name => {
return { name, value: envVars[name] || '' };
}),
sourceFileMap: [],
});
}
return template;
}
}
\ No newline at end of file
import * as vscode from 'vscode';
import { ResultAnalyzer } from '../ResultAnalyzer/ResultAnalyzer';
export interface TestElement {
testItem: vscode.TestItem;
getLabel(): string;
run(options: vscode.TestRun, resultAnalyzer: ResultAnalyzer): Promise<void>;
}
\ No newline at end of file
export class TestResult{
constructor(
public readonly runDuration: number,
public readonly result: string
){}
}
\ No newline at end of file
import * as vscode from 'vscode';
import { ResultAnalyzer } from '../ResultAnalyzer/ResultAnalyzer';
import { CUTETest } from './TestCase';
import { TestElement } from "./TestElement";
export class TestSuite implements TestElement {
private testCases: CUTETest[] = [];
constructor(
public readonly identifier: string,
public readonly testItem : vscode.TestItem
) {}
getLabel(): string {
return this.identifier;
}
async run(options: vscode.TestRun, resultAnalyzer: ResultAnalyzer): Promise<void> {
this.testCases.forEach(testCase => {
testCase.run(options, resultAnalyzer);
});
}
public addTestCase(testCase: CUTETest): void{
testCase.testSuite = this;
this.testCases.push(testCase);
this.testItem.children.add(testCase.testItem);
}
public getTestCases() : CUTETest []{
return this.testCases;
}
public getTestCase(id: string) : CUTETest | undefined {
return this.testCases.find(tc => tc.identifier === id);
}
}
\ No newline at end of file
import * as vscode from 'vscode';
import { TestElement } from "./TestElement";
import { TestSuite } from "./TestSuite";
class TestTree {
private testCases = new Map<string, TestElement>();
set(id : string, data : TestElement) : void {
this.testCases.set(id, data);
}
has(id: string): boolean{
return this.testCases.get(id) !== undefined;
}
get(id: string) : TestElement | undefined{
const data = this.testCases.get(id);
if(data){
return data;
}
const containingSuite = [...this.testCases].find(testCase => {
if(testCase[1] instanceof TestSuite){
return (testCase[1] as TestSuite).getTestCase(id);
}
});
return (containingSuite?.[1] as TestSuite)?.getTestCase(id);
}
clear(controller: vscode.TestController) : void {
this.testCases.forEach(testCase => {
controller.items.delete(testCase.testItem.id);
});
this.testCases.clear();
}
}
\ No newline at end of file
export interface ResultAnalyzer{
analyze(result: string,
onTestSuccess: (testId:string) => void,
onTestFailure: (testId:string, errorMessage:string, line:number) => void,
onTestError: (testId:string, errorMessage:string) => void
) : Promise<void>
}
\ No newline at end of file
......@@ -5,7 +5,9 @@ import { ExecutableSpawner } from '../../utilities/ExecutableSpawner';
class ExecutableTestFinder implements TestFinder{
constructor(private readonly spawner: ExecutableSpawner) { }
public async findTests(context: vscode.ExtensionContext, controller: vscode.TestController, uri: vscode.Uri) {
public async findTests(context: vscode.ExtensionContext, controller: vscode.TestController, uri: vscode.Uri) {
const testData = new TestTree();
const args = ["--display-tests"];
const options = {
cwd: path.dirname(uri.fsPath),
......
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import * as fsp from "fs/promises";
import { CUTETest } from "../Model/TestCase";
import { TestRunner } from "./TestRunner";
import { ExecutableSpawner } from '../../utilities/ExecutableSpawner';
import { TestResult } from '../Model/TestResult';
import { CppDebugger } from '../../utilities/Debugging/CppDebugger';
export class CUTETestRunner implements TestRunner{
constructor(
private readonly spawner: ExecutableSpawner,
private readonly cppDebugger: CppDebugger
) {}
public async runTest(testCase: CUTETest, runId: string): Promise<TestResult> {
const temp = path.join(os.tmpdir(), runId, testCase.identifier);
await fsp.mkdir(temp, { recursive: true });
const args = this.getExecArgs(testCase);
const options = {
cwd: temp,
env: Object.assign({}, process.env),
};
const start = Date.now();
await this.spawner.spawn(vscode.Uri.parse(testCase.execPath.fsPath), args, options);
const runDuration = Date.now() - start;
const testExecutableName = path.basename(testCase.execPath.path);
const resultPath = path.join(temp, `${testExecutableName}.xml`);
const result = await fsp.readFile(resultPath, {encoding:"utf-8"});
await fsp.rm(temp, { recursive: true });
return new TestResult(runDuration, result);
}
debugTest(testCase: CUTETest, runId: string): Promise<TestResult> {
throw new Error('Method not implemented.');
}
private getExecArgs(testCase: CUTETest) : string[]{
let execArgs = [`${testCase.identifier}`];
if(testCase.testSuite){
execArgs = [`${testCase.testSuite.identifier}#${testCase.identifier}`];
}
return execArgs;
}
}
\ No newline at end of file
import { CUTETest } from "../Model/TestCase";
import { TestResult } from "../Model/TestResult";
export interface TestRunner{
runTest(testCase: CUTETest, runId: string): Promise<TestResult>
debugTest(testCase: CUTETest, runId: string): Promise<TestResult>
}
\ No newline at end of file
export interface CppDebugger{
}
\ No newline at end of file
import { CppDebugger } from "./CppDebugger";
export class CpptoolsDebugger extends CppDebugger{
}
\ No newline at end of file
import { CppDebugger } from "./CppDebugger";
export class LLDBDebugger extends CppDebugger{
}
\ No newline at end of file
import { CppDebugger } from "./CppDebugger";
export class WebfreakDebugger extends CppDebugger{
}
\ No newline at end of file
import * as vscode from 'vscode';
export class IDEEnvironment{
public hasExtension(id :string) :boolean {
return vscode.extensions.all.find(e => e.id === id) !== undefined;
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment