diff --git a/README.md b/README.md index adea8a2..4cefb62 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,11 @@ Property | Description `rubyTestExplorer.logfile` | Write diagnostic logs to the given file. `rubyTestExplorer.testFramework` | `none`, `auto`, `rspec`, or `minitest`. `auto` by default, which automatically detects the test framework based on the gems listed by Bundler. Can disable the extension functionality with `none` or set the test framework explicitly, if auto-detect isn't working properly. `rubyTestExplorer.filePattern` | Define the pattern to match test files by, for example `["*_test.rb", "test_*.rb", "*_spec.rb"]`. +`rubyTestExplorer.debugger` | Which debugger to use. `rdebug-ide` or `rdbg`. `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.rdbgCommand` | Define how to run rdbg, for example `rdbg` or `bundle exec rdbg`. `rubyTestExplorer.rspecCommand` | Define the command to run RSpec tests with, for example `bundle exec rspec`, `spring rspec`, or `rspec`. `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. diff --git a/package.json b/package.json index b7fa0d4..9c03969 100644 --- a/package.json +++ b/package.json @@ -141,6 +141,20 @@ }, "scope": "resource" }, + "rubyTestExplorer.debugger": { + "description": "Which debugger to use. `rdebug-ide` or `rdbg`.", + "type": "string", + "default": "rdebug-ide", + "enum": [ + "rdebug-ide", + "rdbg" + ], + "enumDescriptions": [ + "Use ruby-debug-ide gem.", + "Use debug gem." + ], + "scope": "resource" + }, "rubyTestExplorer.debuggerHost": { "markdownDescription": "The host to connect the debugger to.", "default": "127.0.0.1", @@ -158,6 +172,12 @@ "default": "rdebug-ide", "type": "string", "scope": "resource" + }, + "rubyTestExplorer.rdbgCommand": { + "markdownDescription": "Define how to run rdbg, for example `rdbg` or `bundle exec rdbg`.", + "default": "rdbg", + "type": "string", + "scope": "resource" } } } diff --git a/src/adapter.ts b/src/adapter.ts index 89a705c..3a79164 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -72,21 +72,17 @@ export class RubyAdapter implements TestAdapter { async debug(testsToRun: string[]): Promise { this.log.info(`Debugging test(s) ${JSON.stringify(testsToRun)} of ${this.workspace.uri.fsPath}`); - const config = vscode.workspace.getConfiguration('rubyTestExplorer', null) - - const debuggerConfig = { - name: "Debug Ruby Tests", - type: "Ruby", - request: "attach", - remoteHost: config.get('debuggerHost') || "127.0.0.1", - remotePort: config.get('debuggerPort') || "1234", - remoteWorkspaceRoot: "${workspaceRoot}" + let debuggerConfig: vscode.DebugConfiguration; + try { + debuggerConfig = this.getDebuggerConfig(); + } catch (err) { + this.log.error('Failed to get debugger configuration', err); + return; } - const testRunPromise = this.run(testsToRun, debuggerConfig); this.log.info('Starting the debug session'); - let debugSession: any; + let debugSession: vscode.DebugSession; try { await this.testsInstance!.debugCommandStarted() debugSession = await this.startDebugging(debuggerConfig); @@ -103,6 +99,11 @@ export class RubyAdapter implements TestAdapter { subscription.dispose(); }); + if (vscode.workspace.getConfiguration('rubyTestExplorer', null).get('debugger') === 'rdbg') { + // debug gem is configured to stop at load, so we resume the execution on attach. + debugSession.customRequest('continue'); + } + await testRunPromise; } @@ -220,6 +221,44 @@ export class RubyAdapter implements TestAdapter { return vscode.debug.onDidTerminateDebugSession(cb); } + private getDebuggerConfig(): vscode.DebugConfiguration { + const config = vscode.workspace.getConfiguration('rubyTestExplorer', null) + + switch (config.get('debugger')) { + case 'rdebug-ide': { + return { + name: "Debug Ruby Tests", + type: "Ruby", + request: "attach", + remoteHost: config.get('debuggerHost') || "127.0.0.1", + remotePort: config.get('debuggerPort') || "1234", + remoteWorkspaceRoot: "${workspaceRoot}" + } + } + case 'rdbg': { + const debuggerHost = config.get('debuggerHost') || "127.0.0.1"; + const debuggerPort = config.get('debuggerPort') || "1234"; + + // Debugger configuration to attach by using KoichiSasada.vscode-rdbg. + // See: https://marketplace.visualstudio.com/items?itemName=KoichiSasada.vscode-rdbg + return { + name: "Debug Ruby Tests", + type: "rdbg", + request: "attach", + localfs: true, + debugPort: `${debuggerHost}:${debuggerPort}`, + + // These parameters are used to lanuch the debug server. + debuggerHost, + debuggerPort, + } + } + + default: + throw new Error("Invalid debugger"); + } + } + /** * Get the test directory based on the configuration value if there's a configured test framework. */ diff --git a/src/minitestTests.ts b/src/minitestTests.ts index da92249..534faba 100644 --- a/src/minitestTests.ts +++ b/src/minitestTests.ts @@ -70,13 +70,9 @@ export class MinitestTests extends Tests { * @return The rdebug-ide command */ protected getDebugCommand(debuggerConfig: vscode.DebugConfiguration): string { - let command: string = - (vscode.workspace.getConfiguration('rubyTestExplorer', null).get('debugCommand') as string) || - 'rdebug-ide'; - - return ( - `${command} --host ${debuggerConfig.remoteHost} --port ${debuggerConfig.remotePort}` + - ` -- ${process.platform == 'win32' ? '%EXT_DIR%' : '$EXT_DIR'}/debug_minitest.rb` + return this.buildDebugCommand( + debuggerConfig, + `${process.platform == 'win32' ? '%EXT_DIR%' : '$EXT_DIR'}/debug_minitest.rb` ); } diff --git a/src/rspecTests.ts b/src/rspecTests.ts index 12217ca..05e178d 100644 --- a/src/rspecTests.ts +++ b/src/rspecTests.ts @@ -88,13 +88,10 @@ 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'; - - return ( - `${command} --host ${debuggerConfig.remoteHost} --port ${debuggerConfig.remotePort}` + - ` -- ${process.platform == 'win32' ? '%EXT_DIR%' : '$EXT_DIR'}/debug_rspec.rb ${args}` + return this.buildDebugCommand( + debuggerConfig, + `${process.platform == 'win32' ? '%EXT_DIR%' : '$EXT_DIR'}/debug_rspec.rb`, + args ); } /** @@ -192,7 +189,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. @@ -217,7 +215,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); diff --git a/src/tests.ts b/src/tests.ts index ce29bd3..10f1368 100644 --- a/src/tests.ts +++ b/src/tests.ts @@ -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 ((data.startsWith('Fast Debugger') || data.includes("DEBUGGER: wait for debugger connection...")) && this.debugCommandStartedResolver) { this.debugCommandStartedResolver() } }); @@ -519,6 +519,46 @@ export abstract class Tests { return this.context.asAbsolutePath('./ruby'); } + /** + * Get the user-configured rdebug-ide command, if there is one. + * + * @param debuggerConfig A VS Code debugger configuration. + * @return The debugger command + */ + protected buildDebugCommand(debuggerConfig: vscode.DebugConfiguration, initFile: string, args?: string): string { + switch (debuggerConfig.type) { + case "Ruby": { // ruby-debug-ide gem + const debugCommand: string = + (vscode.workspace.getConfiguration('rubyTestExplorer', null).get('debugCommand') as string) || + 'rdebug-ide'; + + let command = `${debugCommand} --host ${debuggerConfig.remoteHost} --port ${debuggerConfig.remotePort} -- ${initFile}` + if (args) { + command += ` ${args}` + } + + return command; + } + case "rdbg": { // debug gem + const debugCommand: string = + (vscode.workspace.getConfiguration('rubyTestExplorer', null).get('rdbgCommand') as string) || + 'rdbg'; + const host = debuggerConfig.debuggerHost as string; + const port = debuggerConfig.debuggerPort as string; + + let command = `${debugCommand} --open --stop-at-load --host ${host} --port ${port} -- ${initFile}` + if (args) { + command += ` ${args}` + } + + return command; + } + default: + throw "Unknown debugger" + } + + } + /** * Runs a single test. *