├── .gitignore ├── tsconfig.json ├── .vscode ├── settings.json └── tasks.json ├── package.json ├── LICENSE ├── src ├── legacy.ts └── index.ts └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "outDir": "out", 6 | "declaration": true 7 | }, 8 | "include": [ 9 | "src/index.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | ".git": true, 4 | "node_modules": true, 5 | "out": true 6 | }, 7 | "editor.insertSpaces": false, 8 | "editor.tabSize": 4, 9 | "[json]": { 10 | "editor.insertSpaces": true, 11 | "editor.tabSize": 2 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Watch API", 6 | "type": "typescript", 7 | "tsconfig": "tsconfig.json", 8 | "option": "watch", 9 | "problemMatcher": [ 10 | "$tsc-watch" 11 | ], 12 | "runOptions": { 13 | "runOn": "folderOpen" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-test-adapter-api", 3 | "description": "API for test adapters for the VS Code test explorer", 4 | "author": "Holger Benl ", 5 | "version": "1.9.0", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/hbenl/vscode-test-adapter-api.git" 10 | }, 11 | "files": [ 12 | "out", 13 | "README.md", 14 | "LICENSE" 15 | ], 16 | "main": "out/index.js", 17 | "types": "out/index.d.ts", 18 | "engines": { 19 | "vscode": "^1.23.0" 20 | }, 21 | "dependencies": {}, 22 | "devDependencies": { 23 | "@types/vscode": "~1.23.0", 24 | "typescript": "^3.9.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Holger Benl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/legacy.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { TestSuiteInfo, TestInfo, TestSuiteEvent, TestEvent } from './index'; 3 | 4 | export interface TestAdapter { 5 | 6 | /** 7 | * The workspace folder that this test adapter is associated with. 8 | * There is usually one test adapter per workspace folder and testing framework. 9 | */ 10 | workspaceFolder?: vscode.WorkspaceFolder; 11 | 12 | /** 13 | * Load the definitions of tests and test suites. 14 | * @returns A promise that is resolved with the test tree (i.e. the hierarchy of suite 15 | * and test definitions) or `undefined` (if no tests were found) or rejected if the test 16 | * definitions could not be loaded. 17 | */ 18 | load(): Promise; 19 | 20 | /** 21 | * This event can be used by the adapter to trigger a reload of the test tree. 22 | */ 23 | readonly reload?: vscode.Event; 24 | 25 | /** 26 | * Run the specified tests. 27 | * @returns A promise that is resolved when the test run is completed. 28 | */ 29 | run(tests: TestSuiteInfo | TestInfo): Promise; 30 | 31 | /** 32 | * Run the specified tests in the debugger. 33 | * @returns A promise that is resolved when the test run is completed. 34 | */ 35 | debug(tests: TestSuiteInfo | TestInfo): Promise; 36 | 37 | /** 38 | * Stop the current test run. 39 | */ 40 | cancel(): void; 41 | 42 | /** 43 | * This event is used by the adapter during a test run to inform the Test Explorer about 44 | * tests and suites being started or completed. 45 | */ 46 | readonly testStates: vscode.Event; 47 | 48 | /** 49 | * This event can be used by the adapter to trigger a test run for all tests that have 50 | * been set to "autorun" in the Test Explorer. 51 | */ 52 | readonly autorun?: vscode.Event; 53 | } 54 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { TestAdapter as LegacyTestAdapter } from './legacy'; 3 | 4 | /** 5 | * The ID of the Test Explorer extension. Use it to get the `TestHub` API like this: 6 | * ``` 7 | const testHub = vscode.extensions.getExtension(testExplorerExtensionId).exports; ``` 8 | * Don't forget to add an `extensionDependencies` entry to your `package.json`: 9 | * ``` 10 | "extensionDependencies": [ "hbenl.vscode-test-explorer" ] ``` 11 | */ 12 | export const testExplorerExtensionId = 'hbenl.vscode-test-explorer'; 13 | 14 | /** 15 | * This is the interface offered by the Test Explorer extension for registering 16 | * and unregistering Test Adapters and Test Controllers. 17 | */ 18 | export interface TestHub { 19 | registerTestAdapter(adapter: TestAdapter): void; 20 | unregisterTestAdapter(adapter: TestAdapter): void; 21 | registerTestController(controller: TestController): void; 22 | unregisterTestController(controller: TestController): void; 23 | 24 | /** 25 | * @deprecated this is for adapters using the pre-1.0 API 26 | */ 27 | registerAdapter(adapter: LegacyTestAdapter): void; 28 | /** 29 | * @deprecated this is for adapters using the pre-1.0 API 30 | */ 31 | unregisterAdapter(adapter: LegacyTestAdapter): void; 32 | } 33 | 34 | /** 35 | * This is the interface that must be implemented by Test Adapters. 36 | */ 37 | export interface TestAdapter { 38 | 39 | /** 40 | * The workspace folder that this test adapter is associated with (if any). 41 | * There is usually one test adapter per workspace folder and testing framework. 42 | */ 43 | workspaceFolder?: vscode.WorkspaceFolder; 44 | 45 | /** 46 | * Start loading the definitions of tests and test suites. 47 | * Note that the Test Adapter should also watch source files and the configuration for changes and 48 | * automatically reload the test definitions if necessary (without waiting for a call to this method). 49 | * @returns A promise that is resolved when the adapter finished loading the test definitions. 50 | */ 51 | load(): Promise; 52 | 53 | /** 54 | * Run the specified tests. 55 | * @param tests An array of test or suite IDs. For every suite ID, all tests in that suite are run. 56 | * @returns A promise that is resolved when the test run is completed. 57 | */ 58 | run(tests: string[]): Promise; 59 | 60 | /** 61 | * Run the specified tests in the debugger. 62 | * @param tests An array of test or suite IDs. For every suite ID, all tests in that suite are run. 63 | * @returns A promise that is resolved when the test run is completed. 64 | */ 65 | debug?(tests: string[]): Promise; 66 | 67 | /** 68 | * Stop the current test run. 69 | */ 70 | cancel(): void; 71 | 72 | /** 73 | * This event is used by the adapter to inform the Test Explorer (and other Test Controllers) 74 | * that it started or finished loading the test definitions. 75 | */ 76 | readonly tests: vscode.Event; 77 | 78 | /** 79 | * This event is used by the adapter during a test run to inform the Test Explorer 80 | * (and other Test Controllers) about a test run and tests and suites being started or completed. 81 | * For example, if there is one test suite with ID `suite1` containing one test with ID `test1`, 82 | * a successful test run would emit the following events: 83 | * ``` 84 | * { type: 'started', tests: ['suite1'] } 85 | * { type: 'suite', suite: 'suite1', state: 'running' } 86 | * { type: 'test', test: 'test1', state: 'running' } 87 | * { type: 'test', test: 'test1', state: 'passed' } 88 | * { type: 'suite', suite: 'suite1', state: 'completed' } 89 | * { type: 'finished' } ``` 90 | */ 91 | readonly testStates: vscode.Event; 92 | 93 | /** 94 | * This event can be used by the adapter to inform the Test Explorer about tests whose states 95 | * are outdated. 96 | * This is usually sent directly after a `TestLoadFinishedEvent` to specify which tests may 97 | * have changed. Furthermore, it should be sent when the source files for the application 98 | * under test have changed. 99 | * This will also trigger a test run for those tests that have been set to "autorun" by the 100 | * user and which are retired by this event. 101 | * If the adapter does not implement this event then the Test Explorer will automatically 102 | * retire (and possibly autorun) all tests after each `TestLoadFinishedEvent`. 103 | */ 104 | readonly retire?: vscode.Event; 105 | 106 | /** 107 | * @deprecated This event is deprecated, use the `retire` event instead. 108 | * For backwards compatibility, `autorun.fire()` calls have the same effect as `retire.fire({})`. 109 | */ 110 | readonly autorun?: vscode.Event; 111 | } 112 | 113 | /** 114 | * This is the interface that must be implemented by Test Controllers 115 | */ 116 | export interface TestController { 117 | 118 | /** 119 | * Register the given Test Adapter. The Test Controller should subscribe to the `adapter.tests` 120 | * event source immediately in order to receive the test definitions. 121 | */ 122 | registerTestAdapter(adapter: TestAdapter): void; 123 | 124 | unregisterTestAdapter(adapter: TestAdapter): void; 125 | } 126 | 127 | /** 128 | * This event is sent by a Test Adapter when it starts loading the test definitions. 129 | */ 130 | export interface TestLoadStartedEvent { 131 | type: 'started'; 132 | } 133 | 134 | /** 135 | * This event is sent by a Test Adapter when it finished loading the test definitions. 136 | */ 137 | export interface TestLoadFinishedEvent { 138 | 139 | type: 'finished'; 140 | 141 | /** The test definitions that have just been loaded */ 142 | suite?: TestSuiteInfo; 143 | 144 | /** If loading the tests failed, this should contain the reason for the failure */ 145 | errorMessage?: string; 146 | } 147 | 148 | /** 149 | * This event is sent by a Test Adapter when it starts a test run. 150 | */ 151 | export interface TestRunStartedEvent { 152 | 153 | type: 'started'; 154 | 155 | /** 156 | * The test(s) that will be run, this should be the same as the `tests` argument from the call 157 | * to `run(tests)` or `debug(tests)` that started the test run. 158 | */ 159 | tests: string[]; 160 | 161 | /** 162 | * A Test Adapter should generate a unique ID for every test run and add that ID to all events 163 | * for that test run. This is necessary so that Test Controllers can link the events if multiple 164 | * test runs are running in parallel. 165 | */ 166 | testRunId?: string; 167 | } 168 | 169 | /** 170 | * This event is sent by a Test Adapter when it finished a test run. 171 | */ 172 | export interface TestRunFinishedEvent { 173 | 174 | type: 'finished'; 175 | 176 | /** 177 | * The ID of the test run that was finished. 178 | */ 179 | testRunId?: string; 180 | } 181 | 182 | /** 183 | * Information about a test suite. 184 | */ 185 | export interface TestSuiteInfo { 186 | 187 | type: 'suite'; 188 | 189 | id: string; 190 | 191 | /** The label to be displayed by the Test Explorer for this suite. */ 192 | label: string; 193 | 194 | /** The description to be displayed next to the label. */ 195 | description?: string; 196 | 197 | /** The tooltip text to be displayed by the Test Explorer when you hover over this suite. */ 198 | tooltip?: string; 199 | 200 | /** 201 | * The file containing this suite (if known). 202 | * This can either be an absolute path (if it is a local file) or a URI. 203 | * Note that this should never contain a `file://` URI. 204 | */ 205 | file?: string; 206 | 207 | /** The line within the specified file where the suite definition starts (if known). */ 208 | line?: number; 209 | 210 | /** Set this to `false` if Test Explorer shouldn't offer debugging this suite. */ 211 | debuggable?: boolean; 212 | 213 | children: (TestSuiteInfo | TestInfo)[]; 214 | 215 | /** Set this to `true` if there was an error while loading the suite */ 216 | errored?: boolean; 217 | 218 | /** 219 | * This message will be displayed by the Test Explorer when the user selects the suite. 220 | * It is usually used for information about why the suite was set to errored. 221 | */ 222 | message?: string; 223 | } 224 | 225 | /** 226 | * Information about a test. 227 | */ 228 | export interface TestInfo { 229 | 230 | type: 'test'; 231 | 232 | id: string; 233 | 234 | /** The label to be displayed by the Test Explorer for this test. */ 235 | label: string; 236 | 237 | /** The description to be displayed next to the label. */ 238 | description?: string; 239 | 240 | /** The tooltip text to be displayed by the Test Explorer when you hover over this test. */ 241 | tooltip?: string; 242 | 243 | /** 244 | * The file containing this test (if known). 245 | * This can either be an absolute path (if it is a local file) or a URI. 246 | * Note that this should never contain a `file://` URI. 247 | */ 248 | file?: string; 249 | 250 | /** The line within the specified file where the test definition starts (if known). */ 251 | line?: number; 252 | 253 | /** Indicates whether this test will be skipped during test runs */ 254 | skipped?: boolean; 255 | 256 | /** Set this to `false` if Test Explorer shouldn't offer debugging this test. */ 257 | debuggable?: boolean; 258 | 259 | /** Set this to `true` if there was an error while loading the test */ 260 | errored?: boolean; 261 | 262 | /** 263 | * This message will be displayed by the Test Explorer when the user selects the test. 264 | * It is usually used for information about why the test was set to errored. 265 | */ 266 | message?: string; 267 | } 268 | 269 | /** 270 | * Information about a suite being started or completed during a test run. 271 | */ 272 | export interface TestSuiteEvent { 273 | 274 | type: 'suite'; 275 | 276 | /** 277 | * The suite that is being started or completed. This field usually contains the ID of the 278 | * suite, but it may also contain the full information about a suite that is started if that 279 | * suite had not been sent to the Test Explorer yet. 280 | */ 281 | suite: string | TestSuiteInfo; 282 | 283 | state: 'running' | 'completed' | 'errored'; 284 | 285 | /** 286 | * This message will be displayed by the Test Explorer when the user selects the suite. 287 | * It is usually used for information about why the suite was set to errored. 288 | */ 289 | message?: string; 290 | 291 | /** 292 | * This property allows you to update the description of the suite in the Test Explorer. 293 | * When the test states are reset, the description will change back to the one from `TestSuiteInfo`. 294 | */ 295 | description?: string; 296 | 297 | /** 298 | * This property allows you to update the tooltip of the suite in the Test Explorer. 299 | * When the test states are reset, the tooltip will change back to the one from `TestSuiteInfo`. 300 | */ 301 | tooltip?: string; 302 | 303 | /** 304 | * This property allows you to update the file of the suite in the Test Explorer. 305 | * When the test states are reset, the file property will change back to the one from `TestSuiteInfo`. 306 | */ 307 | file?: string; 308 | 309 | /** 310 | * This property allows you to update the line of the suite in the Test Explorer. 311 | * When the test states are reset, the line property will change back to the one from `TestSuiteInfo`. 312 | */ 313 | line?: number; 314 | 315 | /** 316 | * The ID of the test run that this event is part of. 317 | */ 318 | testRunId?: string; 319 | } 320 | 321 | /** 322 | * Information about a test being started, completed or skipped during a test run. 323 | */ 324 | export interface TestEvent { 325 | 326 | type: 'test'; 327 | 328 | /** 329 | * The test that is being started, completed or skipped. This field usually contains 330 | * the ID of the test, but it may also contain the full information about a test that is 331 | * started if that test had not been sent to the Test Explorer yet. 332 | */ 333 | test: string | TestInfo; 334 | 335 | state: 'running' | 'passed' | 'failed' | 'skipped' | 'errored'; 336 | 337 | /** 338 | * This message will be displayed by the Test Explorer when the user selects the test. 339 | * It is usually used for information about why a test has failed. 340 | */ 341 | message?: string; 342 | 343 | /** 344 | * These messages will be shown as decorations for the given lines in the editor. 345 | * They are usually used to show information about a test failure at the location of that failure. 346 | */ 347 | decorations?: TestDecoration[]; 348 | 349 | /** 350 | * This property allows you to update the description of the test in the Test Explorer. 351 | * When the test states are reset, the description will change back to the one from `TestInfo`. 352 | */ 353 | description?: string; 354 | 355 | /** 356 | * This property allows you to update the tooltip of the test in the Test Explorer. 357 | * When the test states are reset, the tooltip will change back to the one from `TestInfo`. 358 | */ 359 | tooltip?: string; 360 | 361 | /** 362 | * This property allows you to update the file of the test in the Test Explorer. 363 | * When the test states are reset, the file property will change back to the one from `TestInfo`. 364 | */ 365 | file?: string; 366 | 367 | /** 368 | * This property allows you to update the line of the test in the Test Explorer. 369 | * When the test states are reset, the line property will change back to the one from `TestInfo`. 370 | */ 371 | line?: number; 372 | 373 | /** 374 | * The ID of the test run that this event is part of. 375 | */ 376 | testRunId?: string; 377 | } 378 | 379 | export interface TestDecoration { 380 | 381 | /** 382 | * The line for which the decoration should be shown 383 | */ 384 | line: number; 385 | 386 | /** 387 | * The file in which the decoration should be shown. If this is not set, the decoration will 388 | * be shown in the file containing the test referenced by the TestEvent containing this decoration 389 | * (so in most cases there is no need to set this property). 390 | */ 391 | file?: string; 392 | 393 | /** 394 | * The message to show in the decoration. This must be a single line of text. 395 | */ 396 | message: string; 397 | 398 | /** 399 | * This text is shown when the user hovers over the decoration's message. 400 | * If this isn't defined then the hover will show the test's log. 401 | */ 402 | hover?: string; 403 | } 404 | 405 | export interface RetireEvent { 406 | 407 | /** 408 | * An array of test or suite IDs. For every suite ID, all tests in that suite will be retired. 409 | * If this isn't defined then all tests will be retired. 410 | */ 411 | tests?: string[]; 412 | } 413 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VS Code Test Adapter API 2 | 3 | This package contains the APIs that Test Adapters or Test Controllers need to implement to work with the [VS Code Test Explorer](hhttps://marketplace.visualstudio.com/items?itemName=hbenl.vscode-test-explorer). 4 | 5 | The API reference documentation can be found [here](https://github.com/hbenl/vscode-test-adapter-api/blob/master/src/index.ts). 6 | 7 | ## Implementing a Test Adapter 8 | 9 | A Test Adapter allows the Test Explorer to load and run the tests using a particular test framework. 10 | There is an [Example Test Adapter](https://github.com/hbenl/vscode-example-test-adapter.git) that you can use as a template for your implementation. 11 | It uses the [Test Adapter Util](https://github.com/hbenl/vscode-test-adapter-util.git) package for some standard tasks (mostly logging and registering the adapter with Test Explorer). 12 | 13 | ### Registering your Test Adapter with the Test Explorer 14 | 15 | The easiest way to register your Test Adapter with the Test Explorer is to use the `TestAdapterRegistrar` class from the [Test Adapter Util](https://github.com/hbenl/vscode-test-adapter-util.git) package in the [`activate()`](https://code.visualstudio.com/api/get-started/extension-anatomy#extension-entry-file) function of your extension: 16 | 17 | ```typescript 18 | import { TestHub, testExplorerExtensionId } from 'vscode-test-adapter-api'; 19 | import { TestAdapterRegistrar } from 'vscode-test-adapter-util'; 20 | 21 | export function activate(context: vscode.ExtensionContext) { 22 | 23 | const testExplorerExtension = vscode.extensions.getExtension(testExplorerExtensionId); 24 | 25 | if (testExplorerExtension) { 26 | 27 | const testHub = testExplorerExtension.exports; 28 | 29 | context.subscriptions.push(new TestAdapterRegistrar( 30 | testHub, 31 | workspaceFolder => new MyTestAdapter(workspaceFolder) 32 | )); 33 | } 34 | } 35 | ``` 36 | 37 | The `TestAdapterRegistrar` will create and register one Test Adapter per workspace folder and unregister it when the workspace folder is closed or removed from the workspace. 38 | It requires the Test Adapter to implement a `dispose()` method that will be called after unregistering the Test Adapter. 39 | The `TestAdapterRegistrar` has a `dispose()` method itself which should be called when the Test Adapter extension is deactivated. 40 | In the example above, we achieve this by adding it to `context.subscriptions`. 41 | 42 | While most Test Adapter implementations create one adapter per workspace folder, this is not a requirement. 43 | If you don't want to do this or have any other reason to not use `TestAdapterRegistrar`, you can easily register your adapter like this: 44 | 45 | ```typescript 46 | const testAdapter = new MyTestAdapter(...); 47 | testHub.registerTestAdapter(testAdapter); 48 | ``` 49 | 50 | Don't forget to unregister your adapter when your extension is deactivated: 51 | 52 | ```typescript 53 | testHub.unregisterTestAdapter(testAdapter); 54 | ``` 55 | 56 | ### Implementing the events 57 | 58 | Every Test Adapter needs to implement the `tests` and `testStates` events and it's recommended to also implement the `retire` event: 59 | 60 | ```typescript 61 | private readonly testsEmitter = new vscode.EventEmitter(); 62 | private readonly testStatesEmitter = new vscode.EventEmitter(); 63 | private readonly retireEmitter = new vscode.EventEmitter(); 64 | 65 | get tests(): vscode.Event { 66 | return this.testsEmitter.event; 67 | } 68 | get testStates(): vscode.Event { 69 | return this.testStatesEmitter.event; 70 | } 71 | get retire(): vscode.Event { 72 | return this.retireEmitter.event; 73 | } 74 | ``` 75 | 76 | ### Loading the tests 77 | 78 | The `load()` method is responsible for loading the tests and sending their metadata to the Test Explorer. 79 | This method will be called by the Test Explorer, but you should also consider running it automatically whenever one of the test files has changed (as described [below](#watching-for-changes-in-the-workspace)). 80 | 81 | It uses the `tests` event to communicate with the Test Explorer. 82 | It must send one `TestLoadStartedEvent` at the beginning and one `TestLoadFinishedEvent` at the end. 83 | * If the adapter found some tests, these must be sent using `TestLoadFinishedEvent#suite` 84 | * If the adapter ran successfully but didn't find any tests, the `TestLoadFinishedEvent` should contain no `suite` or `errorMessage` 85 | * If the adapter failed to load the tests (e.g. due to some misconfiguration by the user) and you want to show a message to the user that might help him fix the problem, that message must be sent using `TestLoadFinishedEvent#errorMessage` 86 | * It is important that you ensure that both events are sent (each one exactly once) when loading the tests, otherwise you will confuse the Test Explorer 87 | * Furthermore, you should make sure that the `load()` method doesn't run twice in parallel 88 | * The `TestInfo#id` needs to be unique for each test because otherwise the Test Explorer won't know which test to assign the test results to 89 | * The `TestSuiteInfo#label` of the root suite sent to the Test Explorer is not shown when there is only one adapter, but when there are multiple adapters, it will be shown to let the user know where the tests are coming from, so it is recommended to use the name of the testing framework that your Test Adapter supports as the root suite label 90 | * After sending the `TestLoadFinishedEvent` you should also send a `RetireEvent` to mark the test states as outdated. If your Test Adapter doesn't contain the `retire` property, the Test Explorer will automatically retire all test states after a `TestLoadFinishedEvent`. 91 | 92 | Here's a skeleton for a typical implementation of `load()`: 93 | 94 | ```typescript 95 | private isLoading = false; 96 | 97 | async load(): Promise { 98 | 99 | if (this.isLoading) return; // it is safe to ignore a call to `load()`, even if it comes directly from the Test Explorer 100 | 101 | this.isLoading = true; 102 | this.testsEmitter.fire({ type: 'started' }); 103 | 104 | try { 105 | 106 | const suite = ... // load the tests, the result may be `undefined`... 107 | 108 | this.testsEmitter.fire({ type: 'finished', suite }); 109 | 110 | } catch (e) { 111 | this.testsEmitter.fire({ type: 'finished', errorMessage: util.inspect(e)); 112 | } 113 | 114 | this.retireEmitter.fire({}); 115 | 116 | this.isLoading = false; 117 | } 118 | ``` 119 | 120 | ### Running the tests 121 | 122 | The `run()` method runs the tests in a child process and sends the results to the Test Explorer using the `testStates` event. 123 | It must first send a `TestRunStartedEvent` containing the IDs of the tests that it is going to run. 124 | Next, it should send events for all tests and suites being started or completed. 125 | Finally, it must send a `TestRunFinishedEvent`. 126 | Technically you could run this method automatically (just like you can do with the `load()` method), but this is usually not recommended, 127 | if the user wants tests to be run automatically, he should use the autorun feature of the Test Explorer. 128 | 129 | For example, if there is one test suite with ID `suite1` containing one test with ID `test1`, a successful test run would usually emit the following events: 130 | 131 | ``` 132 | { type: 'started', tests: ['suite1'] } 133 | { type: 'suite', suite: 'suite1', state: 'running' } 134 | { type: 'test', test: 'test1', state: 'running' } 135 | { type: 'test', test: 'test1', state: 'passed' } 136 | { type: 'suite', suite: 'suite1', state: 'completed' } 137 | { type: 'finished' } 138 | ``` 139 | 140 | * The `TestSuiteEvent`s are optional, currently they are only needed if you want to add tests or suites during a test run 141 | * The `TestEvent`s with `state: 'running'` are also optional but recommended so that the user can see which test is currently running 142 | * The `TestEvent`s can be sent in any order, in particular having multiple tests' states set to `'running'` at the same time is supported (useful if the test framework supports running multiple tests in parallel) 143 | * It is important that you ensure that the `TestRunStartedEvent` is the first event and the `TestRunFinishedEvent` is the last event sent when running the tests (and each is sent exactly once), otherwise you will confuse the Test Explorer 144 | * Furthermore, you should make sure that the `run()` method doesn't run twice in parallel 145 | 146 | Here's a skeleton for a typical implementation of `run()` and `cancel()`: 147 | 148 | ```typescript 149 | private runningTestProcess: : child_process.ChildProcess | undefined; 150 | 151 | run(testsToRun: string[]): Promise { 152 | 153 | if (this.runningTestProcess !== undefined) return; // it is safe to ignore a call to `run()` 154 | 155 | this.testStatesEmitter.fire({ type: 'started', tests: testsToRun }); 156 | 157 | return new Promise((resolve, reject) => { 158 | 159 | this.runningTestProcess = child_process.spawn(...); 160 | 161 | // we will _always_ receive an `exit` event when the child process ends, even if it crashes or 162 | // is killed, so this is a good place to send the `TestRunFinishedEvent` and resolve the Promise 163 | this.runningTestProcess.once('exit', () => { 164 | 165 | this.runningTestProcess = undefined; 166 | this.testStatesEmitter.fire({ type: 'finished' }); 167 | resolve(); 168 | 169 | }); 170 | }); 171 | } 172 | 173 | cancel(): void { 174 | if (this.runningTestProcess !== undefined) { 175 | this.runningTestProcess.kill(); 176 | // there is no need to do anything else here because we will receive an `exit` event from the child process 177 | } 178 | } 179 | ``` 180 | 181 | * In this example we use [`child_process.spawn()`](https://nodejs.org/dist/latest-v10.x/docs/api/child_process.html#child_process_child_process_spawn_command_args_options) to start the child process, but if the child process runs javascript, you may want to use [`child_process.fork()`](https://nodejs.org/dist/latest-v10.x/docs/api/child_process.html#child_process_child_process_fork_modulepath_args_options) 182 | * Using [`child_process.exec()`](https://nodejs.org/dist/latest-v10.x/docs/api/child_process.html#child_process_child_process_exec_command_options_callback) or [`child_process.execFile()`](https://nodejs.org/dist/latest-v10.x/docs/api/child_process.html#child_process_child_process_execfile_file_args_options_callback) is not recommended because these functions buffer `stdout` and `stderr`, which has some downsides: 183 | * you won't be able to access the output of the child process until it has finished (i.e. you have to wait until the entire test run is completed before you can show the results to the user) 184 | * the buffers are limited in size and the child process (and hence the test run) will be terminated when this limit is reached 185 | * Don't use any of the [synchronous methods](https://nodejs.org/dist/latest-v10.x/docs/api/child_process.html#child_process_synchronous_process_creation): 186 | these would block the Extension Host process (and hence _all_ VS Code extensions) until the test run is finished 187 | 188 | ### Watching for changes in the workspace 189 | 190 | When a VS Code setting or a source file in the workspace that influences the tests changes, the user can manually reload and/or rerun the tests, but ideally the Test Adapter should do this automatically for him. 191 | 192 | To watch for changes in the VS Code settings, use the [`onDidChangeConfiguration()`](https://code.visualstudio.com/api/references/vscode-api#workspace.onDidChangeConfiguration) event. 193 | 194 | For file changes, there are several options: 195 | * The [`onDidSaveTextDocument()`](https://code.visualstudio.com/api/references/vscode-api#workspace.onDidSaveTextDocument) event is fired by VS Code when the user saves his changes to a text document. 196 | Obviously, this is only useful if you're not interested in files that are generated/changed by another process (like a compiler/transpiler) 197 | * You can use [`createFileSystemWatcher()`](https://code.visualstudio.com/api/references/vscode-api#workspace.createFileSystemWatcher) to watch for file changes in the workspace. 198 | The only downside is that this will not watch files that the user has hidden using the `files.exclude` setting (so this may also miss changes to compiled/transpiled files if the user chose to hide them in VS Code) 199 | * You can use [`chokidar`](https://www.npmjs.com/package/chokidar) to watch for file changes. 200 | This is similar to using `createFileSystemWatcher()` (and VS Code also uses `chokidar` to implement its `FileSystemWatcher`), but it also lets you watch files that are hidden in VS Code. 201 | The main downside is that this is relatively resource-intensive, so you should make sure that you only watch the files that you definitely need to watch (when you use `createFileSystemWatcher()`, 202 | you're "freeloading" on the watcher that VS Code has already set up anyway, so that doesn't use any additional resources) 203 | * If you're watching for changes in files generated by a compiler/transpiler, you may get multiple events in quick succession and should consider using some debouncing mechanism 204 | 205 | To reload the tests after some change, you can simply call your `load()` method yourself. 206 | If the tests themselves haven't changed but their states may be outdated (e.g. if the change was in a source file for the application being tested), you can send a [`RetireEvent`]() to the Test Explorer. 207 | This event will also trigger a test run if the user has enabled the autorun feature for the tests. 208 | If you know that the change may only affect the states of _some_ of the tests, you can send the IDs of these tests in the `RetireEvent`, only those tests will be retired (marked as outdated). 209 | Otherwise you can simply send an empty object as the `RetireEvent` (as in the example below), this will retire all tests. 210 | 211 | This example watches for changes to a VS Code setting `myTestAdapter.testFiles` and reloads the tests when the setting is changed: 212 | 213 | ```typescript 214 | vscode.workspace.onDidChangeConfiguration(configChange => { 215 | if (configChange.affectsConfiguration('myTestAdapter.testFiles', this.workspaceFolder.uri)) { 216 | this.load(); 217 | } 218 | }); 219 | ``` 220 | 221 | This example uses `onDidSaveTextDocument()` to watch for file changes: 222 | 223 | ```typescript 224 | vscode.workspace.onDidSaveTextDocument(document => { 225 | if (isTestFile(document.uri)) { 226 | // the changed file contains tests, so we reload them 227 | this.load(); 228 | } else if (isApplicationFile(document.uri)) { 229 | // the changed file is part of the application being tested, so the test states may be out of date 230 | this.retireEmitter.fire({}); 231 | } 232 | }); 233 | ``` 234 | --------------------------------------------------------------------------------