Skip to content
Open
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ The extension needs Ruby and the `rspec-core` gem installed (and any other depen

By default, you need to have `rspec` installed via Bundler with a `Gemfile` and `bundle install`, otherwise `bundle exec rspec` won't work. If you want to run your Rspec tests with a command other than `bundle exec rspec`, you can configure the command with the `rubyTestExplorer.rspecCommand` setting.

If you use Spring, set `rubyTestExplorer.rspecCommand` to `spring rspec` for normal test runs. Debug runs can use `rubyTestExplorer.rspecDebugCommand` and `rubyTestExplorer.debuggerConfig` when the default `rdebug-ide` wrapper is not flexible enough, for example:

```json
"rubyTestExplorer.rspecDebugCommand": "bundle exec rdbg --open --host ${host} --port ${port} -c -- spring rspec ${formatterArgs}",
"rubyTestExplorer.debuggerConfig": {
"type": "rdbg",
"request": "attach",
"debugPort": "${host}:${port}"
}
```

### Minitest

The extension needs Ruby and the `minitest` gem installed (and any other dependencies required by your test suite). It's been tested with Ruby 2.5 and 2.6, and Minitest 5.x. It should work with most recent versions of Ruby and Minitest.
Expand All @@ -39,6 +50,8 @@ Currently supported:
- Live test status updates as the test suite runs.
- File locations for each test.
- Configurable RSpec command.
- Configurable RSpec debug command.
- Configurable debug attach settings.
- Configurable RSpec `spec/` directory.
- Configurable Minitest command.
- Configurable Minitest `test/` directory.
Expand All @@ -59,7 +72,9 @@ Property | Description
`rubyTestExplorer.debuggerHost` | Define the host to connect the debugger to, for example `127.0.0.1`.
`rubyTestExplorer.debuggerPort` | Define the port to connect the debugger to, for example `1234`.
`rubyTestExplorer.debugCommand` | Define how to run rdebug-ide, for example `rdebug-ide` or `bundle exec rdebug-ide`.
`rubyTestExplorer.debuggerConfig` | Optionally override the VS Code debug configuration used to attach to the test process. String values support `${host}` and `${port}` placeholders. Useful for alternative debuggers such as `rdbg`.
`rubyTestExplorer.rspecCommand` | Define the command to run RSpec tests with, for example `bundle exec rspec`, `spring rspec`, or `rspec`.
`rubyTestExplorer.rspecDebugCommand` | Optionally define the full command used to debug RSpec tests. Supports `${debugCommand}`, `${host}`, `${port}`, `${debugPort}`, `${rubyScriptsDir}`, and `${formatterArgs}` placeholders. Useful for Spring or rdbg setups.
`rubyTestExplorer.rspecDirectory` | Define the relative directory of the specs in a given workspace, for example `./spec/`.
`rubyTestExplorer.minitestCommand` | Define how to run Minitest with Rake, for example `./bin/rake`, `bundle exec rake` or `rake`. Must be a Rake command.
`rubyTestExplorer.minitestDirectory` | Define the relative location of your `test` directory, for example `./test/`.
Expand Down
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,18 @@
"default": "rdebug-ide",
"type": "string",
"scope": "resource"
},
"rubyTestExplorer.debuggerConfig": {
"markdownDescription": "Optionally override the VS Code debug configuration used to attach to the test process. String values support `${host}` and `${port}` placeholders. Useful for alternative debuggers such as `rdbg`.",
"default": {},
"type": "object",
"scope": "resource"
},
"rubyTestExplorer.rspecDebugCommand": {
"markdownDescription": "Optionally define the full command used to debug RSpec tests. Supports `${debugCommand}`, `${host}`, `${port}`, `${debugPort}`, `${rubyScriptsDir}`, and `${formatterArgs}` placeholders. Useful for Spring or rdbg setups, for example `bundle exec rdbg --open --host ${host} --port ${port} -c -- spring rspec ${formatterArgs}`.",
"default": "",
"type": "string",
"scope": "resource"
}
}
}
Expand Down
33 changes: 30 additions & 3 deletions src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,23 @@ export class RubyAdapter implements TestAdapter {
this.log.info(`Debugging test(s) ${JSON.stringify(testsToRun)} of ${this.workspace.uri.fsPath}`);

const config = vscode.workspace.getConfiguration('rubyTestExplorer', null)
const debuggerHost = config.get('debuggerHost') || "127.0.0.1";
const debuggerPort = config.get('debuggerPort') || "1234";

const debuggerConfig = {
const defaultDebuggerConfig = {
name: "Debug Ruby Tests",
type: "Ruby",
request: "attach",
remoteHost: config.get('debuggerHost') || "127.0.0.1",
remotePort: config.get('debuggerPort') || "1234",
remoteHost: debuggerHost,
remotePort: debuggerPort,
remoteWorkspaceRoot: "${workspaceRoot}"
}
const debuggerConfigOverride = (config.get('debuggerConfig') as any) || {};
const debuggerConfig = this.interpolateDebugConfig(
Object.assign({}, defaultDebuggerConfig, debuggerConfigOverride),
String(debuggerHost),
String(debuggerPort)
);

const testRunPromise = this.run(testsToRun, debuggerConfig);

Expand Down Expand Up @@ -187,6 +195,25 @@ export class RubyAdapter implements TestAdapter {
}
}

protected interpolateDebugConfig(config: any, host: string, port: string): any {
if (typeof config === 'string') {
return config
.replace(/\$\{host\}/g, host)
.replace(/\$\{port\}/g, port);
}
if (Array.isArray(config)) {
return config.map((item) => this.interpolateDebugConfig(item, host, port));
}
if (config && typeof config === 'object') {
const interpolated: any = {};
for (const key of Object.keys(config)) {
interpolated[key] = this.interpolateDebugConfig(config[key], host, port);
}
return interpolated;
}
return config;
}

protected async startDebugging(debuggerConfig: vscode.DebugConfiguration): Promise<vscode.DebugSession> {
const debugSessionPromise = new Promise<vscode.DebugSession>((resolve, reject) => {

Expand Down
29 changes: 23 additions & 6 deletions src/rspecTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ export class RspecTests extends Tests {
// Allow a buffer of 64MB.
const execArgs: childProcess.ExecOptions = {
cwd: this.workspace.uri.fsPath,
maxBuffer: 8192 * 8192
maxBuffer: 8192 * 8192,
env: this.getProcessEnv()
};

childProcess.exec(cmd, execArgs, (err, stdout) => {
Expand Down Expand Up @@ -88,9 +89,23 @@ export class RspecTests extends Tests {
* @return The rdebug-ide command
*/
protected getDebugCommand(debuggerConfig: vscode.DebugConfiguration, args: string): string {
let command: string =
(vscode.workspace.getConfiguration('rubyTestExplorer', null).get('debugCommand') as string) ||
'rdebug-ide';
const config = vscode.workspace.getConfiguration('rubyTestExplorer', null);
let command: string = (config.get('debugCommand') as string) || 'rdebug-ide';
let template: string = (config.get('rspecDebugCommand') as string) || '';

if (template.trim()) {
return template.replace(/\$\{(\w+)\}/g, (_match, name) => {
const replacements: { [key: string]: string } = {
debugCommand: command,
host: String(debuggerConfig.remoteHost),
port: String(debuggerConfig.remotePort),
debugPort: `${debuggerConfig.remoteHost}:${debuggerConfig.remotePort}`,
rubyScriptsDir: process.platform == 'win32' ? '%EXT_DIR%' : '$EXT_DIR',
formatterArgs: args,
};
return replacements[name] ?? _match;
});
}

return (
`${command} --host ${debuggerConfig.remoteHost} --port ${debuggerConfig.remotePort}` +
Expand Down Expand Up @@ -192,7 +207,8 @@ export class RspecTests extends Tests {
this.log.info(`Running test file: ${testFile}`);
const spawnArgs: childProcess.SpawnOptions = {
cwd: this.workspace.uri.fsPath,
shell: true
shell: true,
env: this.getProcessEnv()
};

// Run tests for a given file at once with a single command.
Expand All @@ -217,7 +233,8 @@ export class RspecTests extends Tests {
this.log.info(`Running full test suite.`);
const spawnArgs: childProcess.SpawnOptions = {
cwd: this.workspace.uri.fsPath,
shell: true
shell: true,
env: this.getProcessEnv()
};

let testCommand = this.testCommandWithFormatterAndDebugger(debuggerConfig);
Expand Down
9 changes: 8 additions & 1 deletion src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ export abstract class Tests {
this.currentChildProcess.stderr!.pipe(split2()).on('data', (data) => {
data = data.toString();
this.log.debug(`[CHILD PROCESS OUTPUT] ${data}`);
if (data.startsWith('Fast Debugger') && this.debugCommandStartedResolver) {
if (this.isDebugCommandReady(data) && this.debugCommandStartedResolver) {
this.debugCommandStartedResolver()
}
});
Expand Down Expand Up @@ -510,6 +510,13 @@ export abstract class Tests {
})
}

protected isDebugCommandReady(data: string): boolean {
return data.startsWith('Fast Debugger')
|| data.startsWith('DEBUGGER: Debugger can attach')
|| data.startsWith('DEBUGGER: wait for debugger connection')
|| data.startsWith('DEBUGGER: Connected');
}

/**
* Get the absolute path of the custom_formatter.rb file.
*
Expand Down