diff --git a/package.json b/package.json index ed7caf8..799b25d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "cucumber-java-test-runner", "displayName": "Cucumber Test Runner for Java", "description": "Run and debug Cucumber BDD scenarios from VS Code's native Test Explorer. Integrates with Maven and cucumber-junit-platform-engine.", - "version": "0.2.0", + "version": "0.2.1", "publisher": "arunkris", "license": "MIT", "repository": { diff --git a/src/execution/mavenRunner.ts b/src/execution/mavenRunner.ts index 5f78265..cd580bd 100644 --- a/src/execution/mavenRunner.ts +++ b/src/execution/mavenRunner.ts @@ -6,6 +6,7 @@ import * as config from '../config/configuration'; const RESULTS_FILENAME = 'cucumber-vscode-results.json'; const CLASSPATH_FILENAME = 'cp.txt'; +const CLI_ARGFILE = 'cucumber-cli-cp.txt'; const JUNIT_PLATFORM_PROPERTIES = 'junit-platform.properties'; export class MavenRunner implements BuildToolRunner { @@ -127,10 +128,12 @@ export class MavenRunner implements BuildToolRunner { assembleCucumberCliCommand(options: RunOptions): CommandSpec { const java = this.resolveJavaExecutable(); - const classpath = this.resolveTestClasspath(options.projectRoot); const resultsPath = this.getResultsFilePath(options.projectRoot); - const args: string[] = ['-cp', classpath, 'io.cucumber.core.cli.Main']; + // Write classpath to an argfile to avoid Windows' 32K command line limit. + // Use forward slashes — Java's argfile parser treats backslashes as escapes. + const argFilePath = this.writeClasspathArgFile(options.projectRoot); + const args: string[] = [`@${argFilePath.replace(/\\/g, '/')}`, 'io.cucumber.core.cli.Main']; args.push('--plugin', `json:${resultsPath.replace(/\\/g, '/')}`); @@ -189,21 +192,26 @@ export class MavenRunner implements BuildToolRunner { private resolveJavaExecutable(): string { const javaHome = process.env.JAVA_HOME; + const exe = process.platform === 'win32' ? 'java.exe' : 'java'; if (javaHome) { - const javaBin = path.join(javaHome, 'bin', 'java'); - if (fs.existsSync(javaBin) || fs.existsSync(javaBin + '.exe')) { + const javaBin = path.join(javaHome, 'bin', exe); + if (fs.existsSync(javaBin)) { return javaBin; } } - return 'java'; + return exe; } - private resolveTestClasspath(projectRoot: string): string { + private writeClasspathArgFile(projectRoot: string): string { const cpFile = path.join(projectRoot, 'target', CLASSPATH_FILENAME); const deps = fs.readFileSync(cpFile, 'utf-8').trim(); const sep = process.platform === 'win32' ? ';' : ':'; const testClasses = path.join(projectRoot, 'target', 'test-classes'); const classes = path.join(projectRoot, 'target', 'classes'); - return [testClasses, classes, deps].filter(Boolean).join(sep); + const classpath = [testClasses, classes, deps].filter(Boolean).join(sep); + + const argFile = path.join(projectRoot, 'target', CLI_ARGFILE); + fs.writeFileSync(argFile, `-cp\n${classpath.replace(/\\/g, '/')}\n`); + return argFile; } } diff --git a/src/execution/testExecutor.ts b/src/execution/testExecutor.ts index ed9bea0..5e8ea30 100644 --- a/src/execution/testExecutor.ts +++ b/src/execution/testExecutor.ts @@ -97,20 +97,28 @@ export class TestExecutor { const resultsPath = this.buildToolRunner.getResultsFilePath(projectRoot); this.deleteFileIfExists(resultsPath); - if (featureTargets.length > 0) { - // Specific scenarios: compile then run Cucumber CLI directly - if (debug) { - await this.executeCucumberCliDebug(runOptions, run, cancellation); + try { + if (featureTargets.length > 0) { + if (debug) { + await this.executeCucumberCliDebug(runOptions, run, cancellation); + } else { + await this.executeCucumberCli(runOptions, run, cancellation); + } } else { - await this.executeCucumberCli(runOptions, run, cancellation); + if (debug) { + await this.executeDebug(runOptions, projectRoot, run, cancellation); + } else { + await this.executeRun(runOptions, run, cancellation); + } } - } else { - // Run All: Maven test with runner class - if (debug) { - await this.executeDebug(runOptions, projectRoot, run, cancellation); - } else { - await this.executeRun(runOptions, run, cancellation); + } catch (execErr) { + const msg = execErr instanceof Error ? execErr.message : String(execErr); + this.logger.error('Execution failed', execErr); + for (const item of projectItems) { + run.errored(item, new vscode.TestMessage(`Execution failed: ${msg}`)); + reportedItems.add(item.id); } + continue; } if (!cancellation.isCancellationRequested) { diff --git a/src/test/unit/mavenRunner.test.ts b/src/test/unit/mavenRunner.test.ts index 2b71688..af3a791 100644 --- a/src/test/unit/mavenRunner.test.ts +++ b/src/test/unit/mavenRunner.test.ts @@ -407,7 +407,7 @@ describe('MavenRunner', () => { assert.equal(cmd.args[tagsIdx + 1], 'not @wip'); }); - it('builds classpath from cp.txt + target dirs', () => { + it('writes classpath argfile with target dirs and dependencies', () => { mkdirp(path.join(tmpDir, 'target')); fs.writeFileSync(path.join(tmpDir, 'target', 'cp.txt'), '/dep1.jar:/dep2.jar'); @@ -415,11 +415,18 @@ describe('MavenRunner', () => { projectRoot: tmpDir, featureTargets: ['f.feature:1'], }); - const cpIdx = cmd.args.indexOf('-cp'); - const cp = cmd.args[cpIdx + 1]; - assert.ok(cp.includes('test-classes'), 'Classpath should include target/test-classes'); - assert.ok(cp.includes('classes'), 'Classpath should include target/classes'); - assert.ok(cp.includes('dep1.jar'), 'Classpath should include dependencies'); + // First arg should be @argfile reference + assert.ok(cmd.args[0].startsWith('@'), 'First arg should be @argfile'); + assert.ok(cmd.args[0].includes('cucumber-cli-cp.txt')); + + // Read the argfile and verify classpath content + const argFilePath = cmd.args[0].substring(1); // strip @ + const argContent = fs.readFileSync(argFilePath, 'utf-8'); + assert.ok(argContent.startsWith('-cp\n'), 'Argfile should start with -cp'); + assert.ok(argContent.includes('test-classes'), 'Classpath should include target/test-classes'); + assert.ok(argContent.includes('classes'), 'Classpath should include target/classes'); + assert.ok(argContent.includes('dep1.jar'), 'Classpath should include dependencies'); + assert.ok(!argContent.includes('\\'), 'Argfile should use forward slashes'); }); });