From 172f95e725f38aa58ff7928df73e3889a59e150c Mon Sep 17 00:00:00 2001 From: Elon Mallin Date: Tue, 21 Nov 2023 23:33:10 +0100 Subject: [PATCH 1/2] fix: add arg builder and enable code lens --- src/CodeLens/PhpCodeLensProvider.ts | 16 +++- src/CodeLens/PhpunitXmlCodeLensProvider.ts | 16 +++- src/PhpunitCommand/PhpunitArgBuilder.ts | 104 +++++++++++++++++++++ src/extension.ts | 33 ++++--- src/phpunittest.ts | 50 ++++++++++ src/test/php-project/phpunit.xml | 50 +++++----- src/test/suite/codelens.test.ts | 11 ++- 7 files changed, 229 insertions(+), 51 deletions(-) create mode 100644 src/PhpunitCommand/PhpunitArgBuilder.ts diff --git a/src/CodeLens/PhpCodeLensProvider.ts b/src/CodeLens/PhpCodeLensProvider.ts index c53f531..a162df1 100644 --- a/src/CodeLens/PhpCodeLensProvider.ts +++ b/src/CodeLens/PhpCodeLensProvider.ts @@ -1,5 +1,6 @@ import { CodeLensProvider, CodeLens, CancellationToken, TextDocument, Range } from 'vscode'; import { Class, CommentBlock, Engine, Identifier, Method, Namespace } from 'php-parser'; +import { PhpunitArgBuilder } from '../PhpunitCommand/PhpunitArgBuilder'; let lastDocumentText: string; let lastCodeLenses: Array = []; @@ -40,6 +41,10 @@ export class PhpCodeLensProvider implements CodeLensProvider { } } + for (const codeLens of codeLenses) { + (codeLens.command!.arguments![0] as PhpunitArgBuilder).withDirectoryOrFile(document.fileName); + } + lastDocumentText = document.getText(); lastCodeLenses = codeLenses; @@ -79,11 +84,15 @@ export class PhpCodeLensProvider implements CodeLensProvider { if (codeLenses.length > 0) { const classCodeLensRange = new Range(node.loc!.start.line - 1, 0, node.loc!.start.line - 1, 0); + const className = typeof node.name === 'string' ? node.name : (node.name as Identifier).name; codeLenses.push(new CodeLens(classCodeLensRange, { command: 'phpunit.Test', title: "Run tests", - arguments: ["AdditionTest"], + arguments: [ + new PhpunitArgBuilder() + .withFilter(className) + ], })); } @@ -107,7 +116,10 @@ export class PhpCodeLensProvider implements CodeLensProvider { return new CodeLens(codeLensRange, { command: 'phpunit.Test', title: 'Run test', - arguments: [methodName], + arguments: [ + new PhpunitArgBuilder() + .withFilter(methodName) + ], }); } } diff --git a/src/CodeLens/PhpunitXmlCodeLensProvider.ts b/src/CodeLens/PhpunitXmlCodeLensProvider.ts index 5d3f476..c3e92dc 100644 --- a/src/CodeLens/PhpunitXmlCodeLensProvider.ts +++ b/src/CodeLens/PhpunitXmlCodeLensProvider.ts @@ -1,6 +1,7 @@ import { CodeLensProvider, CodeLens, CancellationToken, TextDocument, Range } from 'vscode'; import { parse, DocumentCstNode } from '@xml-tools/parser'; import { buildAst, XMLElement, } from '@xml-tools/ast'; +import { PhpunitArgBuilder } from '../PhpunitCommand/PhpunitArgBuilder'; let lastDocumentText: string; let lastCodeLenses: Array = []; @@ -22,7 +23,7 @@ export class PhpunitXmlCodeLensProvider implements CodeLensProvider { for (const node of ast.rootElement!.subElements) { if (node.name === 'testsuites') { - codeLenses.push(...this.parseTestSuites(node)); + codeLenses.push(...this.parseTestSuites(node, document.fileName)); } } @@ -37,7 +38,7 @@ export class PhpunitXmlCodeLensProvider implements CodeLensProvider { // return codeLens; // } - private parseTestSuites(node: XMLElement): CodeLens[] { + private parseTestSuites(node: XMLElement, fileName: string): CodeLens[] { const codeLenses: Array = []; for (const child of node.subElements) { @@ -48,10 +49,14 @@ export class PhpunitXmlCodeLensProvider implements CodeLensProvider { if (codeLenses.length > 0) { const codeLensRange = new Range(node.position.startLine - 1, 0, node.position.startLine - 1, 0); + codeLenses.push(new CodeLens(codeLensRange, { command: 'phpunit.Test', title: 'Run tests', - arguments: ['All Test Suites'], + arguments: [ + new PhpunitArgBuilder() + .withConfig(fileName) + ] })); } @@ -65,7 +70,10 @@ export class PhpunitXmlCodeLensProvider implements CodeLensProvider { return new CodeLens(codeLensRange, { command: 'phpunit.Test', title: 'Run test', - arguments: [name], + arguments: [ + new PhpunitArgBuilder() + .withSuite(name) + ] }); } } diff --git a/src/PhpunitCommand/PhpunitArgBuilder.ts b/src/PhpunitCommand/PhpunitArgBuilder.ts new file mode 100644 index 0000000..b50ec3f --- /dev/null +++ b/src/PhpunitCommand/PhpunitArgBuilder.ts @@ -0,0 +1,104 @@ +import escapeStringRegexp from "../Utils/escape-string-regexp"; + +export class PhpunitArgBuilder { + private directoryOrFiles: Array = []; + private suites: Array = []; + private filters: Array = []; + private groups: Array = []; + private configFile?: string; + private color?: string; + private args: Array = []; + private pathMappings?: { [key: string]: string }; + private workspaceFolder?: string; + + public withDirectoryOrFile(directoryOrFile: string): PhpunitArgBuilder { + this.directoryOrFiles.push(directoryOrFile.replace(/\\/gi, "/")); + + return this; + } + + public withSuite(suiteName: string): PhpunitArgBuilder { + this.suites.push(suiteName); + + return this; + } + + public withSuites(suiteNames: Array): PhpunitArgBuilder { + this.suites.push(...suiteNames); + + return this; + } + + public withFilter(filter: string): PhpunitArgBuilder { + this.filters.push(filter); + + return this; + } + + public withGroup(group: string): PhpunitArgBuilder { + this.groups.push(group); + + return this; + } + + public withGroups(groups: Array): PhpunitArgBuilder { + this.groups.push(...groups); + + return this; + } + + public withConfig(configFile: string): PhpunitArgBuilder { + this.configFile = configFile.replace(/\\/gi, "/"); + + return this; + } + + public withColors(color: 'never' | 'auto' | 'always'): PhpunitArgBuilder { + this.color = color; + + return this; + } + + public withArgs(args: string[]): PhpunitArgBuilder { + this.args.push(...args); + + return this; + } + + public withPathMappings(pathMappings: { [key: string]: string }, workspaceFolder: string): PhpunitArgBuilder { + this.pathMappings = pathMappings; + this.workspaceFolder = workspaceFolder; + + return this; + } + + public buildArgs(): Array { + let args = [ + this.configFile ? `--configuration ${this.configFile}` : '', + this.color ? `--colors=${this.color}` : '', + this.suites.length > 0 ? `--testsuite ${this.suites.join(',')}` : '', + this.filters.map(filter => `--filter ${filter}`).join(' '), + this.groups.length > 0 ? `--group ${this.groups.join(',')}` : '', + this.args.join(' '), + this.directoryOrFiles.join(' '), + ] + .filter(part => part); + + if (this.pathMappings) { + for (const key of Object.keys(this.pathMappings)) { + const localPath = key + .replace(/\$\{workspaceFolder\}/gi, this.workspaceFolder!) + .replace(/\\/gi, "/"); + const remotePath = this.pathMappings[key]; + + args = args.map(arg => arg.replace(new RegExp(escapeStringRegexp(localPath), "ig"), remotePath)); + } + } + + return args.filter(part => part); + } + + public build(): string { + return this.buildArgs().join(' '); + } +} diff --git a/src/extension.ts b/src/extension.ts index d885107..3c4fe95 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,8 +4,9 @@ import * as vscode from "vscode"; import { TestRunner } from "./phpunittest"; import { IMyExtensionApi } from "./MyExtensionApi"; import path = require("path"); -// import { PhpCodeLensProvider } from "./CodeLens/PhpCodeLensProvider"; -// import { PhpunitXmlCodeLensProvider } from "./CodeLens/PhpunitXmlCodeLensProvider"; +import { PhpunitArgBuilder } from "./PhpunitCommand/PhpunitArgBuilder"; +import { PhpCodeLensProvider } from "./CodeLens/PhpCodeLensProvider"; +import { PhpunitXmlCodeLensProvider } from "./CodeLens/PhpunitXmlCodeLensProvider"; export function activate(context: vscode.ExtensionContext): IMyExtensionApi { const testOutputFile = path.resolve(vscode.workspace.workspaceFolders![0].uri.fsPath, 'test-output.txt'); @@ -30,8 +31,12 @@ export function activate(context: vscode.ExtensionContext): IMyExtensionApi { }); context.subscriptions.push( - vscode.commands.registerCommand("phpunit.Test", async () => { - return await PHPUnitTestRunner.run("test"); + vscode.commands.registerCommand("phpunit.Test", async (argBuilder: PhpunitArgBuilder) => { + if (argBuilder) { + return await PHPUnitTestRunner.runArgs(argBuilder); + } else { + return await PHPUnitTestRunner.run("test"); + } }) ); @@ -91,16 +96,16 @@ export function activate(context: vscode.ExtensionContext): IMyExtensionApi { }) ); - // context.subscriptions.push(vscode.languages.registerCodeLensProvider({ - // language: 'php', - // scheme: 'file', - // pattern: '**/test*/**/*.php' - // }, new PhpCodeLensProvider())); - // context.subscriptions.push(vscode.languages.registerCodeLensProvider({ - // language: 'xml', - // scheme: 'file', - // pattern: '**/phpunit.xml*' - // }, new PhpunitXmlCodeLensProvider())); + context.subscriptions.push(vscode.languages.registerCodeLensProvider({ + language: 'php', + scheme: 'file', + pattern: '**/test*/**/*.php' + }, new PhpCodeLensProvider())); + context.subscriptions.push(vscode.languages.registerCodeLensProvider({ + language: 'xml', + scheme: 'file', + pattern: '**/phpunit.xml*' + }, new PhpunitXmlCodeLensProvider())); return myExtensionApi; } diff --git a/src/phpunittest.ts b/src/phpunittest.ts index d00f415..a65133e 100644 --- a/src/phpunittest.ts +++ b/src/phpunittest.ts @@ -9,6 +9,7 @@ import IPhpUnitDriver from "./Drivers/IPhpUnitDriver"; import PhpUnitDrivers from "./Drivers/PhpUnitDrivers"; import { IExtensionBootstrapBridge } from "./ExtensionBootstrapBridge"; import parsePhpToObject from "./PhpParser/PhpParser"; +import { PhpunitArgBuilder } from "./PhpunitCommand/PhpunitArgBuilder"; type RunType = | "test" @@ -281,6 +282,55 @@ export class TestRunner { return undefined; } + public async runArgs(argBuilder: PhpunitArgBuilder) { + const config = vscode.workspace.getConfiguration("phpunit"); + const order = config.get("driverPriority"); + + const driver = await this.getDriver(order); + if (!driver) { + console.error(`Wasn't able to start phpunit.`); + return; + } + + const configArgs = config.get("args", []); + argBuilder.withArgs(configArgs); + + const colors = config.get("colors"); + if (colors && (configArgs.indexOf(colors) === -1)) { + argBuilder.withColors(colors.replace(/--colors=?/i, '') as 'never' | 'auto' | 'always'); + } + + const pathMappings = config.get<{ [key: string]: string }>("paths"); + if (pathMappings) { + argBuilder.withPathMappings(pathMappings, vscode.workspace.workspaceFolders![0].uri.fsPath); + } + + const runConfig = await driver.run(argBuilder.buildArgs()); + + if (config.get("clearOutputOnRun")) { + this.channel.clear(); + } + this.channel.appendLine(`Running phpunit with driver: ${driver.name}`); + this.channel.appendLine(runConfig.command); + + this.bootstrapBridge.setTaskCommand( + runConfig.command, + runConfig.problemMatcher + ); + + if (process.env.VSCODE_PHPUNIT_TEST === 'true') { + console.debug(runConfig.command); + } + + await vscode.commands.executeCommand("workbench.action.terminal.clear"); + await vscode.commands.executeCommand( + "workbench.action.tasks.runTask", + "phpunit: run" + ); + + this.channel.show(true); + } + public async run(type: RunType) { const config = vscode.workspace.getConfiguration("phpunit"); const order = config.get("driverPriority"); diff --git a/src/test/php-project/phpunit.xml b/src/test/php-project/phpunit.xml index 844f996..6271c2d 100644 --- a/src/test/php-project/phpunit.xml +++ b/src/test/php-project/phpunit.xml @@ -1,29 +1,27 @@ - - - - - - ./tests - - - ./tests/Science - - - - - - ./src - - + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.4/phpunit.xsd" + bootstrap="vendor/autoload.php" + cacheDirectory=".phpunit.cache" + executionOrder="depends,defects" + requireCoverageMetadata="true" + beStrictAboutCoverageMetadata="true" + beStrictAboutOutputDuringTests="true" + failOnRisky="false" + failOnWarning="true" + colors="true"> + + + tests/Math + + + tests/Science + + + + + + src + + diff --git a/src/test/suite/codelens.test.ts b/src/test/suite/codelens.test.ts index 086cec7..a94d9f5 100644 --- a/src/test/suite/codelens.test.ts +++ b/src/test/suite/codelens.test.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import path = require('path'); import { PhpCodeLensProvider } from '../../CodeLens/PhpCodeLensProvider'; import { PhpunitXmlCodeLensProvider } from '../../CodeLens/PhpunitXmlCodeLensProvider'; +import { PhpunitArgBuilder } from '../../PhpunitCommand/PhpunitArgBuilder'; suite('CodeLens Test Suite', () => { @@ -20,11 +21,11 @@ suite('CodeLens Test Suite', () => { assert.equal(codeLenses[0].command?.command, 'phpunit.Test'); assert.equal(codeLenses[0].command?.title, 'Run test'); - assert.equal(codeLenses[0].command?.arguments?.[0], 'testAdd'); + assert.match((codeLenses[0].command?.arguments?.[0] as PhpunitArgBuilder).build(), /--filter testAdd/i); assert.equal(codeLenses[1].command?.command, 'phpunit.Test'); assert.equal(codeLenses[1].command?.title, 'Run tests'); - assert.equal(codeLenses[1].command?.arguments?.[0], 'AdditionTest'); + assert.match((codeLenses[1].command?.arguments?.[0] as PhpunitArgBuilder).build(), /--filter AdditionTest/i); }); test('Test phpunit.xml', async () => { @@ -37,14 +38,14 @@ suite('CodeLens Test Suite', () => { assert.equal(codeLenses[0].command?.command, 'phpunit.Test'); assert.equal(codeLenses[0].command?.title, 'Run test'); - assert.equal(codeLenses[0].command?.arguments?.[0], 'Test All'); + assert.match((codeLenses[0].command?.arguments?.[0] as PhpunitArgBuilder).build(), /--testsuite Math/i); assert.equal(codeLenses[1].command?.command, 'phpunit.Test'); assert.equal(codeLenses[1].command?.title, 'Run test'); - assert.equal(codeLenses[1].command?.arguments?.[0], 'Science'); + assert.match((codeLenses[1].command?.arguments?.[0] as PhpunitArgBuilder).build(), /--testsuite Science/i); assert.equal(codeLenses[2].command?.command, 'phpunit.Test'); assert.equal(codeLenses[2].command?.title, 'Run tests'); - assert.equal(codeLenses[2].command?.arguments?.[0], 'All Test Suites'); + assert.match((codeLenses[2].command?.arguments?.[0] as PhpunitArgBuilder).build(), /--configuration .*phpunit.xml/i); }); }); From 26388d8adf16e7d1e20e2267f5d91c6bbbf08433 Mon Sep 17 00:00:00 2001 From: Elon Mallin Date: Wed, 22 Nov 2023 23:14:38 +0100 Subject: [PATCH 2/2] refactor: arg builder method names --- src/CodeLens/PhpCodeLensProvider.ts | 6 +++--- src/CodeLens/PhpunitXmlCodeLensProvider.ts | 7 ++++--- src/PhpunitCommand/PhpunitArgBuilder.ts | 14 +++++++------- src/phpunittest.ts | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/CodeLens/PhpCodeLensProvider.ts b/src/CodeLens/PhpCodeLensProvider.ts index a162df1..bc651ba 100644 --- a/src/CodeLens/PhpCodeLensProvider.ts +++ b/src/CodeLens/PhpCodeLensProvider.ts @@ -42,7 +42,7 @@ export class PhpCodeLensProvider implements CodeLensProvider { } for (const codeLens of codeLenses) { - (codeLens.command!.arguments![0] as PhpunitArgBuilder).withDirectoryOrFile(document.fileName); + (codeLens.command!.arguments![0] as PhpunitArgBuilder).addDirectoryOrFile(document.fileName); } lastDocumentText = document.getText(); @@ -91,7 +91,7 @@ export class PhpCodeLensProvider implements CodeLensProvider { title: "Run tests", arguments: [ new PhpunitArgBuilder() - .withFilter(className) + .addFilter(className) ], })); } @@ -118,7 +118,7 @@ export class PhpCodeLensProvider implements CodeLensProvider { title: 'Run test', arguments: [ new PhpunitArgBuilder() - .withFilter(methodName) + .addFilter(methodName) ], }); } diff --git a/src/CodeLens/PhpunitXmlCodeLensProvider.ts b/src/CodeLens/PhpunitXmlCodeLensProvider.ts index c3e92dc..f08b94e 100644 --- a/src/CodeLens/PhpunitXmlCodeLensProvider.ts +++ b/src/CodeLens/PhpunitXmlCodeLensProvider.ts @@ -43,7 +43,7 @@ export class PhpunitXmlCodeLensProvider implements CodeLensProvider { for (const child of node.subElements) { if (child.name === 'testsuite') { - codeLenses.push(this.parseTestSuite(child)); + codeLenses.push(this.parseTestSuite(child, fileName)); } } @@ -63,7 +63,7 @@ export class PhpunitXmlCodeLensProvider implements CodeLensProvider { return codeLenses; } - private parseTestSuite(node: XMLElement): CodeLens { + private parseTestSuite(node: XMLElement, fileName: string): CodeLens { const codeLensRange = new Range(node.position.startLine - 1, 0, node.position.startLine - 1, 0); const name = node.attributes.find((attribute) => attribute.key === 'name')!.value!; @@ -72,7 +72,8 @@ export class PhpunitXmlCodeLensProvider implements CodeLensProvider { title: 'Run test', arguments: [ new PhpunitArgBuilder() - .withSuite(name) + .withConfig(fileName) + .addSuite(name) ] }); } diff --git a/src/PhpunitCommand/PhpunitArgBuilder.ts b/src/PhpunitCommand/PhpunitArgBuilder.ts index b50ec3f..5087e69 100644 --- a/src/PhpunitCommand/PhpunitArgBuilder.ts +++ b/src/PhpunitCommand/PhpunitArgBuilder.ts @@ -11,37 +11,37 @@ export class PhpunitArgBuilder { private pathMappings?: { [key: string]: string }; private workspaceFolder?: string; - public withDirectoryOrFile(directoryOrFile: string): PhpunitArgBuilder { + public addDirectoryOrFile(directoryOrFile: string): PhpunitArgBuilder { this.directoryOrFiles.push(directoryOrFile.replace(/\\/gi, "/")); return this; } - public withSuite(suiteName: string): PhpunitArgBuilder { + public addSuite(suiteName: string): PhpunitArgBuilder { this.suites.push(suiteName); return this; } - public withSuites(suiteNames: Array): PhpunitArgBuilder { + public addSuites(suiteNames: Array): PhpunitArgBuilder { this.suites.push(...suiteNames); return this; } - public withFilter(filter: string): PhpunitArgBuilder { + public addFilter(filter: string): PhpunitArgBuilder { this.filters.push(filter); return this; } - public withGroup(group: string): PhpunitArgBuilder { + public addGroup(group: string): PhpunitArgBuilder { this.groups.push(group); return this; } - public withGroups(groups: Array): PhpunitArgBuilder { + public addGroups(groups: Array): PhpunitArgBuilder { this.groups.push(...groups); return this; @@ -59,7 +59,7 @@ export class PhpunitArgBuilder { return this; } - public withArgs(args: string[]): PhpunitArgBuilder { + public addArgs(args: string[]): PhpunitArgBuilder { this.args.push(...args); return this; diff --git a/src/phpunittest.ts b/src/phpunittest.ts index a65133e..80aa990 100644 --- a/src/phpunittest.ts +++ b/src/phpunittest.ts @@ -293,7 +293,7 @@ export class TestRunner { } const configArgs = config.get("args", []); - argBuilder.withArgs(configArgs); + argBuilder.addArgs(configArgs); const colors = config.get("colors"); if (colors && (configArgs.indexOf(colors) === -1)) {