Skip to content
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
22 changes: 15 additions & 7 deletions src/execution/mavenRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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, '/')}`);

Expand Down Expand Up @@ -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;
}
}
30 changes: 19 additions & 11 deletions src/execution/testExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
19 changes: 13 additions & 6 deletions src/test/unit/mavenRunner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,19 +407,26 @@ 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');

const cmd = runner.assembleCucumberCliCommand({
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');
});
});

Expand Down