[release] prepare v0.25.0 release

c7fcd42 .github/workflows: change tabs to spaces
f5ef2de .github/workflows: install stable delve version for testing
c522170 test/integration/goDebug.test.ts: fix listening for output event
ad0e420 src/goDebugFactory: log console message with info level
07fe3d3 src/goDebugFactory: disable logDest support on windows
a7f91bb src/goTools: update dlv-dap version
4e3fc58 src/goMain: overwrite process.env[GOROOT] if go.goroot is set
730d202 test/integration/goDebug: make logDest file less flaky
a73da23 test/integration/goDebug: output trace for dlv dap cleanup test
c807011 Revert "Revert "src/goDebugFactory: use --log-dest to capture dlv logs and add logDest""
7bfa543 src/goLanguageServer: disable the language features for unrecognized workspace types
a031e96 src/goDebugConfiguration.ts: use `program` to determine mode if file
9005b0c package.json: recognize 'stopOnEntry' in attach mode
8fa9dbd src/goDebugConfiguration.ts: disable warning about using 'cwd' in dap
cdbf062 goLanguageServer: only prompt for the survey when working on Go code
5f1faa3 goSurvey: pull survey logic out of goLanguageServer.ts
b1cb6c5 src/util: adjust go dev version regex
ae07a73 test/integration/goDebug.test.ts: set 'output' for tests
bdd4acd src/goLanguageServer: prompt for the survey regardless of gopls usage
e3bcd86 src/goDebugFactory: send SIGINT to delve and avoid treekill
acc65d3 src/goDebugConfiguration.ts: show warning for dlv-dap with dlvLoadConfig
7a1a338 test/integration/goDebug: don't wait for debug client to stop in teardown
4463476 test/integration/goDebug: get goroutines reliably in switch goroutine test
57ba505 debugAdapter: fix missing file bug for remote debugging
c5ca0dc test/integration/goDebug: skip more remote attach tests
a496b7a test/integration/goDebug: skip remote attach tests
78b5f53 test/integration/goDebug: verify goroutines running before switch
5e9eef4 Revert "src/goDebugFactory: use --log-dest to capture dlv logs and add logDest"
71410c4 src/goDebugFactory: use --log-dest to capture dlv logs and add logDest
b2f6fcf test/integration: disable broken setSelectedGo test
64ad2e0 src/goDebugFactory: start dlv dap process early
2e81d29 .github: stop sending feature requests to discussions/slacks
2875e05 package.json: add command to run 'go mod init'
5bcfcac test/integration/goDebug: set mode to 'debug' or 'test' not 'auto'
9bc6048 test/integration/goDebug.ts: check for incorrect output event noDebug
b7797c3 src/goDebugConfiguration.ts: replace resolvePath with resolveHomeDir
ec08530 src/debugAdapter: accept additional trace levels
5639184 src/welcome: avoid vscode.Uri.joinPath
aabfc9f src/goTools: update dlv-dap @v1.6.1-0.20210419181450-2408ed87bf87
5efb087 test/integration/statusbar: fix/delete broken tests
e0e1e60 src/goDebugFactory: send teardown log to Go Debug channel
cfc787e src/goDebugFactory: add GoDebugAdapterTrackerFactory
8755d84 src/goLogging: extend logging facility
afa59f6 src/goTest.ts: add debug previous command
015d002 src/goInstallTools: use GOBIN correctly installing dlv-dap/gocode-gomod
10509fb src/goCheck: add missing goplsConfig parameter to goLint
4b6f857 package.json: set editor.suggest.snippetsPreventQuickSuggestions to false
a9b31d9 [release] Update CHANGELOG and LICENSE for v0.24.1
86f704a src/goDebugConfiguration: auto update dlv-dap if autoUpdates enabled
bd55732 src/goDebugFactory.ts: send the first error when starting dlv-dap
f607db9 test/runTest: stop creating ${workspaceFolder} directory

Change-Id: I89fc378ea4a30900fc3bda5fc11aedc6bcff71f4
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 5cffadb..bbcbbe7 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -7,8 +7,6 @@
 
 ---
 
-If you have a feature request, please share your idea on the [GitHub Discussion](https://github.com/golang/vscode-go/discussions/categories/ideas), or on the [`#vscode` channel](https://invite.slack.golangbridge.org/messages/vscode) in Gophers Slack first.
-
 **Is your feature request related to a problem? Please describe.**
 A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
 
diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml
index cc9d8f6..8a2681b 100644
--- a/.github/workflows/release-nightly.yml
+++ b/.github/workflows/release-nightly.yml
@@ -65,7 +65,7 @@
             go get github.com/ramya-rao-a/go-outline
             go get github.com/go-delve/delve/cmd/dlv@master
             cp "${HOME}/go/bin/dlv${{env.EXT}}" "${HOME}/go/bin/dlv-dap${{env.EXT}}"
-
+            go get github.com/go-delve/delve/cmd/dlv
         env:
           GO111MODULE: on
           EXT: "${{ matrix.os == 'windows-latest' && '.exe' || ''}}"
diff --git a/.github/workflows/test-long-all.yml b/.github/workflows/test-long-all.yml
index 20ea53c..b47da3f 100644
--- a/.github/workflows/test-long-all.yml
+++ b/.github/workflows/test-long-all.yml
@@ -61,7 +61,7 @@
             go get github.com/ramya-rao-a/go-outline
             go get github.com/go-delve/delve/cmd/dlv@master
             cp "${HOME}/go/bin/dlv${{env.EXT}}" "${HOME}/go/bin/dlv-dap${{env.EXT}}"
-
+            go get github.com/go-delve/delve/cmd/dlv
         env:
           GO111MODULE: on
           EXT: "${{ matrix.os == 'windows-latest' && '.exe' || ''}}"
diff --git a/.github/workflows/test-long.yml b/.github/workflows/test-long.yml
index fa77e01..7f450b4 100644
--- a/.github/workflows/test-long.yml
+++ b/.github/workflows/test-long.yml
@@ -60,6 +60,7 @@
             go get github.com/ramya-rao-a/go-outline
             go get github.com/go-delve/delve/cmd/dlv@master
             cp "${HOME}/go/bin/dlv${{env.EXT}}" "${HOME}/go/bin/dlv-dap${{env.EXT}}"
+            go get github.com/go-delve/delve/cmd/dlv
         env:
           GO111MODULE: on
           EXT: "${{ matrix.os == 'windows-latest' && '.exe' || ''}}"
diff --git a/.github/workflows/test-smoke.yml b/.github/workflows/test-smoke.yml
index 523d40a..83ce5a7 100644
--- a/.github/workflows/test-smoke.yml
+++ b/.github/workflows/test-smoke.yml
@@ -58,6 +58,7 @@
             go get github.com/ramya-rao-a/go-outline
             go get github.com/go-delve/delve/cmd/dlv@master
             cp "${HOME}/go/bin/dlv${{env.EXT}}"  "${HOME}/go/bin/dlv-dap${{env.EXT}}"
+            go get github.com/go-delve/delve/cmd/dlv
         env:
           GO111MODULE: on
           EXT: "${{ matrix.os == 'windows-latest' && '.exe' || ''}}"
diff --git a/docs/commands.md b/docs/commands.md
index 9e4cbe5..6486617 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -71,6 +71,10 @@
 
 Re-runs the last executed test.
 
+### `Go: Debug Previous`
+
+Re-runs the last debugged test run through a codelens or "Go: Debug Test at Cursor" command.
+
 ### `Go: Toggle Test Coverage In Current Package`
 
 Displays test coverage in the current package.
@@ -175,6 +179,10 @@
 
 Install the current package.
 
+### `Go: Initialize go.mod`
+
+Run `go mod init` in the workspace folder.
+
 ### `Go: Cancel Running Tests`
 
 Cancels running tests.
diff --git a/docs/debugging.md b/docs/debugging.md
index a66ae0d..5e81b51 100644
--- a/docs/debugging.md
+++ b/docs/debugging.md
@@ -78,7 +78,7 @@
 
 ## Launch Configurations
 
-To get started debugging, run the command `Debug: Open launch.json`. If you did not already have a `launch.json` file for your project, this will create one for you. It will contain this default configuration, which can be used to debug the current package.
+To get started debugging, run the command `Debug: Open launch.json`. If you did not already have a `launch.json` file for your project, this will create one for you. It will contain this default configuration, which can be used to debug the current package. With mode `auto`, the file that is currently open will determine whether to debug the program as a test. If `program` is instead set to a Go file, that file will determine which mode to run in.
 
 ```json5
 {
@@ -109,10 +109,11 @@
 env        | Environment variables to use when debugging. Use the format: `{ "NAME": "VALUE" }`. Not applicable to `attach` requests.
 envFile    | Absolute path to a file containing environment variable definitions. The environment variables passed in via the `env` property override the ones in this file.
 args       | Array of command-line arguments to pass to the program being debugged.
-showLog    | If `true`, Delve logs will be printed in the Debug Console panel. This corresponds to `dlv`'s `--log` flag.
+showLog    | If `true` and `logDest` is not set, Delve logs will be printed in the Debug Console panel. If `true` and `logDest` is set, logs will be written to the `logDest` file. This corresponds to `dlv`'s `--log` flag.
 logOutput  | Comma-separated list of Delve components (`debugger`, `gdbwire`, `lldbout`, `debuglineerr`, `rpc`) that should produce debug output when `showLog` is `true`. This corresponds to `dlv`'s `--log-output` flag.
+logDest    | Absolute path to the delve log output file. This corresponds to `dlv`'s `--log-dest` flag, but number (used for file descriptor) is disallowed. Supported only in dlv-dap mode on Linux and Mac.
 buildFlags | Build flags to pass to the Go compiler. This corresponds to `dlv`'s `--build-flags` flag.
-dlvFlags   | Extra flags passed to `dlv`. See `dlv help` for the full list of supported flags. This is useful when users need to pass less commonly used or new flags such as `--only-same-user`, `--check-go-version`. Note that some flags such as `--log-output`, `--log`, `--init`, `--api-version` already have corresponding properties in the debug configuration, and flags such as `--listen` and `--headless` are used internally. If they are specified in `dlvFlags`, they may be ignored or cause an error.
+dlvFlags   | Extra flags passed to `dlv`. See `dlv help` for the full list of supported flags. This is useful when users need to pass less commonly used or new flags such as `--only-same-user`, `--check-go-version`. Note that some flags such as `--log-output`, `--log`, `--log-dest`, `--init`, `--api-version` already have corresponding properties in the debug configuration, and flags such as `--listen` and `--headless` are used internally. If they are specified in `dlvFlags`, they may be ignored or cause an error.
 remotePath | If remote debugging (`mode`: `remote`), this should be the absolute path to the package being debugged on the remote machine. See the section on [Remote Debugging](#remote-debugging) for further details. [golang/vscode-go#45](https://github.com/golang/vscode-go/issues/45) is also relevant. Becomes the first mapping in substitutePath.
 substitutePath | An array of mappings from an absolute local path to an absolute remote path that is used by the debuggee. The debug adapter will replace the local path with the remote path in all of the calls. The mappings are applied in order, and the first matching mapping is used. This can be used to map files that have moved since the program was built, different remote paths, and symlinked files or directories. This is intended to be equivalent to the [substitute-path](https://github.com/go-delve/delve/tree/master/Documentation/cli#config) configuration, and will eventually configure substitute-path in Delve directly.
 cwd | The working directory to be used in running the program. If remote debugging (`mode`: `remote`), this should be the absolute path to the working directory being debugged on the local machine. See the section on [Remote Debugging](#remote-debugging) for further details. [golang/vscode-go#45](https://github.com/golang/vscode-go/issues/45) is also relevant.
@@ -437,7 +438,7 @@
     "name": "Launch remote",
     "type": "go",
     "request": "launch",
-    "mode": "auto",
+    "mode": "debug",
     "program": "/path/to/hello",
     "substitutePath": [
 		{
diff --git a/docs/dlv-dap.md b/docs/dlv-dap.md
index 7a4d91f..c4168ad 100644
--- a/docs/dlv-dap.md
+++ b/docs/dlv-dap.md
@@ -56,6 +56,7 @@
 🎉  The new debug adapter offers many improvements and fixes bugs that existed in the old adapter. [Here](https://github.com/golang/vscode-go/issues?q=is%3Aissue+label%3Afixedindlvdaponly) is a partial list of enhancement/fixes available only in the new adapter.
 
 * User-friendly inlined presentation of variables of all complex types (map, struct, pointer, array, slice, ...)
+* Auto-loading of nested variables without extra configuration. (See [delve PR/2455](https://github.com/go-delve/delve/pull/2455#issuecomment-827884652))
 * Fixed handling of maps with compound keys
 * Improved CALL STACK presentation
 * Fixed automated "Add to Watch" / "Copy as Expression" expressions.
@@ -69,11 +70,11 @@
 
 ⚒️ The following features are still under development. 
 
-* Stop/pause/restart while the debugged program is running does not work yet.
-* Cannot be used with `debug test` codelens.
-* Support for `"dlvFlags"` attributes in launch configuration is not available.
+* Pause/restart while the debugged program is running does not work yet.
+* Setting breakpoints while the debugged program is running does not work yet.
+* `SetVariable` does not work yet.
 * `dlvLoadConfig` to configure max string/bytes length in [`"go.delveConfig"`](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#configuration) does not work.
-* [Remote debugging](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#remote-debugging) is not supported.
+* Traditional [remote debugging](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#remote-debugging) is not supported.
 
 Follow along with [golang/vscode-go#23](https://github.com/golang/vscode-go/issues/23) and the [project dashboard](https://github.com/golang/vscode-go/projects/3) for updates on the implementation.
 
@@ -87,7 +88,7 @@
 Please report issues in [our issue tracker](https://github.com/golang/vscode-go/issues) with the following information.
 
 * `go version`
-* `go version -m dlv-dap`
+* `go version -m <path/to/dlv-dap>`
 * VS Code and VS Code Go version.
 * Instruction to reproduce the issue (code snippets, your `launch.json`, screenshot)
 
@@ -106,7 +107,7 @@
 }
 ```
 
-Set `logOutput` and `showLog` attributes in `launch.json` to enable logging and DAP message tracing.
+Set `logOutput` and `showLog` attributes in `launch.json` to enable `dlv'-side logging and DAP message tracing.
 ```json5
 {
     "name": "Launch file",
@@ -118,6 +119,19 @@
 }
 ```
 
+Set `trace` attribute to control the verbosity of debug extension's logging.
+The logging will appear in the `Go Debug` output channel (Command Palette -> "View: Toggle Output" -> Select "Go Debug" from the dropdown menu).
+
+```json5
+{
+    "name": "Launch file",
+    "type": "go",
+    "debugAdapter": "dlv-dap",
+    "trace": "verbose",
+    ...
+}
+```
+
 If you are having issues with seeing logs and or suspect problems in extension's integration, you can start Delve DAP server from a separate terminal and configure the extension to directly connect to it.
 
 ```
diff --git a/docs/settings.md b/docs/settings.md
index cd432d2..e5f254a 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -144,7 +144,7 @@
 | --- | --- |
 | `apiVersion` | Delve Api Version to use. Default value is 2. <br/> Allowed Options: `1`, `2` <br/> Default: `2` |
 | `debugAdapter` | Select which debug adapter to use by default. This is also used for choosing which debug adapter to use when no launch.json is present and with codelenses. <br/> Allowed Options: `legacy`, `dlv-dap` <br/> Default: `"legacy"` |
-| `dlvLoadConfig` | LoadConfig describes to delve, how to load values from target's memory <br/> Default: ``` { <pre>"followPointers" :	true,<br/>"maxArrayValues" :	64,<br/>"maxStringLen" :	64,<br/>"maxStructFields" :	-1,<br/>"maxVariableRecurse" :	1,</pre>} ``` |
+| `dlvLoadConfig` | LoadConfig describes to delve, how to load values from target's memory. Ignored by 'dlv-dap'. <br/> Default: ``` { <pre>"followPointers" :	true,<br/>"maxArrayValues" :	64,<br/>"maxStringLen" :	64,<br/>"maxStructFields" :	-1,<br/>"maxVariableRecurse" :	1,</pre>} ``` |
 | `showGlobalVariables` | Boolean value to indicate whether global package variables should be shown in the variables pane or not. <br/> Default: `false` |
 
 Default:
diff --git a/package-lock.json b/package-lock.json
index f00776f..06162f8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "go",
-  "version": "0.24.1",
+  "version": "0.25.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "go",
-      "version": "0.24.1",
+      "version": "0.25.0",
       "license": "MIT",
       "dependencies": {
         "deep-equal": "^2.0.2",
diff --git a/package.json b/package.json
index dde3a52..dadc3ce 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "go",
   "displayName": "Go",
-  "version": "0.24.2",
+  "version": "0.25.0",
   "publisher": "golang",
   "description": "Rich Go language support for Visual Studio Code",
   "author": {
@@ -96,6 +96,7 @@
     "onCommand:go.tools.install",
     "onCommand:go.locate.tools",
     "onCommand:go.show.commands",
+    "onCommand:go.run.modinit",
     "onDebugInitialConfigurations",
     "onDebugResolve:go",
     "onWebviewPanel:welcomeGo"
@@ -157,7 +158,8 @@
         "editor.formatOnSave": true,
         "editor.codeActionsOnSave": {
           "source.organizeImports": true
-        }
+        },
+        "editor.suggest.snippetsPreventQuickSuggestions": false
       }
     },
     "commands": [
@@ -222,6 +224,11 @@
         "description": "Re-runs the last executed test."
       },
       {
+        "command": "go.debug.previous",
+        "title": "Go: Debug Previous",
+        "description": "Re-runs the last debugged test run through a codelens or \"Go: Debug Test at Cursor\" command."
+      },
+      {
         "command": "go.test.coverage",
         "title": "Go: Toggle Test Coverage In Current Package",
         "description": "Displays test coverage in the current package."
@@ -352,6 +359,11 @@
         "description": "Install the current package."
       },
       {
+        "command": "go.run.modinit",
+        "title": "Go: Initialize go.mod",
+        "description": "Run `go mod init` in the workspace folder."
+      },
+      {
         "command": "go.test.cancel",
         "title": "Go: Cancel Running Tests",
         "description": "Cancels running tests."
@@ -576,7 +588,7 @@
               },
               "dlvFlags": {
                 "type": "array",
-                "description": "Extra flags for `dlv`. See `dlv help` for the full list of supported. Flags such as `--log-output`, `--log`, `--init`, `--api-version`, `--output`, `--backend` already have corresponding properties in the debug configuration, and flags such as `--listen` and `--headless` are used internally. If they are specified in `dlvFlags`, they may be ignored or cause an error.",
+                "description": "Extra flags for `dlv`. See `dlv help` for the full list of supported. Flags such as `--log-output`, `--log`, `--log-dest`, `--init`, `--api-version`, `--output`, `--backend` already have corresponding properties in the debug configuration, and flags such as `--listen` and `--headless` are used internally. If they are specified in `dlvFlags`, they may be ignored or cause an error.",
                 "items": {
                   "type": "string"
                 },
@@ -600,12 +612,15 @@
               "trace": {
                 "type": "string",
                 "enum": [
-                  "log",
                   "verbose",
+                  "trace",
+                  "log",
+                  "info",
+                  "warn",
                   "error"
                 ],
                 "default": "error",
-                "description": "Various levels of logging shown in the debug console. When set to 'log' or 'verbose', the logs will also be written to a file."
+                "description": "Various levels of the debug console & 'Go Debug' output channel. When using the `legacy` debug adapter, the logs will also be written to a file if it is set to a value other than `error`."
               },
               "envFile": {
                 "type": [
@@ -645,6 +660,10 @@
                 "description": "Comma separated list of components that should produce debug output. Maps to dlv's `--log-output` flag. Check `dlv log` for details.",
                 "default": "debugger"
               },
+              "logDest": {
+                "type": "string",
+                "description": "dlv's `--log-dest` flag. See `dlv log` for details. Number argument is not allowed. Supported only on Linux and Mac OS in dlv-dap mode."
+              },
               "dlvLoadConfig": {
                 "type": "object",
                 "properties": {
@@ -674,7 +693,7 @@
                     "default": -1
                   }
                 },
-                "description": "LoadConfig describes to delve, how to load values from target's memory",
+                "description": "LoadConfig describes to delve, how to load values from target's memory. Ignored by 'dlv-dap'",
                 "default": {
                   "followPointers": true,
                   "maxVariableRecurse": 1,
@@ -744,9 +763,14 @@
                 "description": "Indicates local or remote debugging. Local maps to the dlv 'attach' command, remote maps to 'connect'.",
                 "default": "local"
               },
+              "stopOnEntry": {
+                "type": "boolean",
+                "description": "Automatically stop program after attach.",
+                "default": false
+              },
               "dlvFlags": {
                 "type": "array",
-                "description": "Extra flags for `dlv`. See `dlv help` for the full list of supported. Flags such as `--log-output` and `--log` already have corresponding properties in the debug configuration, and flags such as `--listen` and `--headless` are used internally. If they are specified in `dlvFlags`, they may be ignored or cause an error.",
+                "description": "Extra flags for `dlv`. See `dlv help` for the full list of supported. Flags such as `--log-output`, `--log`, `--log-dest`, `--init`, `--api-version`, `--output`, `--backend` already have corresponding properties in the debug configuration, and flags such as `--listen` and `--headless` are used internally. If they are specified in `dlvFlags`, they may be ignored or cause an error.",
                 "items": {
                   "type": "string"
                 },
@@ -829,6 +853,10 @@
                 "description": "Comma separated list of components that should produce debug output. Maps to dlv's `--log-output` flag. Check `dlv log` for details.",
                 "default": "debugger"
               },
+              "logDest": {
+                "type": "string",
+                "description": "dlv's `--log-dest` flag. See `dlv log` for details. Number argument is not allowed. Supported only on Linux and Mac OS in dlv-dap mode."
+              },
               "dlvLoadConfig": {
                 "type": "object",
                 "properties": {
@@ -858,7 +886,7 @@
                     "default": -1
                   }
                 },
-                "description": "LoadConfig describes to delve, how to load values from target's memory",
+                "description": "LoadConfig describes to delve, how to load values from target's memory. Ignored by 'dlv-dap'.",
                 "default": {
                   "followPointers": true,
                   "maxVariableRecurse": 1,
@@ -1668,7 +1696,7 @@
                   "default": -1
                 }
               },
-              "description": "LoadConfig describes to delve, how to load values from target's memory",
+              "description": "LoadConfig describes to delve, how to load values from target's memory. Ignored by 'dlv-dap'.",
               "default": {
                 "followPointers": true,
                 "maxVariableRecurse": 1,
diff --git a/src/debugAdapter/goDebug.ts b/src/debugAdapter/goDebug.ts
index 515e0a0..4beb6bc 100644
--- a/src/debugAdapter/goDebug.ts
+++ b/src/debugAdapter/goDebug.ts
@@ -281,7 +281,12 @@
 	host?: string;
 	buildFlags?: string;
 	init?: string;
-	trace?: 'verbose' | 'log' | 'error';
+	// trace, info, warn are to match goLogging.
+	// In practice, this adapter handles only verbose, log, and error
+	//  verbose === trace,
+	//  log === info === warn,
+	//  error
+	trace?: 'verbose' | 'trace' | 'info' | 'log' | 'warn' | 'error';
 	backend?: string;
 	output?: string;
 	substitutePath?: { from: string; to: string }[];
@@ -315,7 +320,7 @@
 	remotePath?: string;
 	port?: number;
 	host?: string;
-	trace?: 'verbose' | 'log' | 'error';
+	trace?: 'verbose' | 'trace' | 'info' | 'log' | 'warn' | 'error';
 	backend?: string;
 	substitutePath?: { from: string; to: string }[];
 	/** Delve LoadConfig parameters */
@@ -1126,6 +1131,11 @@
 	 * Cache the result in remoteToLocalPathMapping.
 	 */
 	protected inferLocalPathFromRemotePath(remotePath: string): string | undefined {
+		// Don't try to infer a path for a file that does not exist
+		if (remotePath === '') {
+			return remotePath;
+		}
+
 		if (this.remoteToLocalPathMapping.has(remotePath)) {
 			return this.remoteToLocalPathMapping.get(remotePath);
 		}
@@ -1954,9 +1964,9 @@
 		args: LaunchRequestArguments | AttachRequestArguments
 	) {
 		this.logLevel =
-			args.trace === 'verbose'
+			args.trace === 'verbose' || args.trace === 'trace'
 				? Logger.LogLevel.Verbose
-				: args.trace === 'log'
+				: args.trace === 'log' || args.trace === 'info' || args.trace === 'warn'
 				? Logger.LogLevel.Log
 				: Logger.LogLevel.Error;
 		const logPath =
diff --git a/src/goDebugConfiguration.ts b/src/goDebugConfiguration.ts
index 0dcef51..dad70f3 100644
--- a/src/goDebugConfiguration.ts
+++ b/src/goDebugConfiguration.ts
@@ -11,13 +11,20 @@
 import vscode = require('vscode');
 import { getGoConfig } from './config';
 import { toolExecutionEnvironment } from './goEnv';
-import { declinedToolInstall, promptForMissingTool, promptForUpdatingTool, shouldUpdateTool } from './goInstallTools';
+import {
+	declinedToolInstall,
+	installTools,
+	promptForMissingTool,
+	promptForUpdatingTool,
+	shouldUpdateTool
+} from './goInstallTools';
 import { packagePathToGoModPathMap } from './goModules';
 import { getTool, getToolAtVersion } from './goTools';
 import { pickProcess, pickProcessByName } from './pickProcess';
 import { getFromGlobalState, updateGlobalState } from './stateUtils';
-import { getBinPath, resolvePath } from './util';
+import { getBinPath, getGoVersion } from './util';
 import { parseEnvFiles } from './utils/envUtils';
+import { resolveHomeDir } from './utils/pathUtils';
 
 let dlvDAPVersionCurrent = false;
 
@@ -136,6 +143,14 @@
 
 		const goConfig = getGoConfig(folder && folder.uri);
 		const dlvConfig = goConfig['delveConfig'];
+
+		// Figure out which debugAdapter is being used first, so we can use this to send warnings
+		// for properties that don't apply.
+		if (!debugConfiguration.hasOwnProperty('debugAdapter') && dlvConfig.hasOwnProperty('debugAdapter')) {
+			debugConfiguration['debugAdapter'] = dlvConfig['debugAdapter'];
+		}
+		const debugAdapter = debugConfiguration['debugAdapter'] === 'dlv-dap' ? 'dlv-dap' : 'dlv';
+
 		let useApiV1 = false;
 		if (debugConfiguration.hasOwnProperty('useApiV1')) {
 			useApiV1 = debugConfiguration['useApiV1'] === true;
@@ -148,6 +163,17 @@
 		if (!debugConfiguration.hasOwnProperty('apiVersion') && dlvConfig.hasOwnProperty('apiVersion')) {
 			debugConfiguration['apiVersion'] = dlvConfig['apiVersion'];
 		}
+		if (
+			debugAdapter === 'dlv-dap' &&
+			(debugConfiguration.hasOwnProperty('dlvLoadConfig') ||
+				goConfig.inspect('delveConfig.dlvLoadConfig').globalValue !== undefined ||
+				goConfig.inspect('delveConfig.dlvLoadConfig').workspaceValue !== undefined)
+		) {
+			this.showWarning(
+				'ignoreDebugDlvConfigWithDlvDapWarning',
+				"User specified 'dlvLoadConfig' setting will be ignored by debug adapter 'dlv-dap'."
+			);
+		}
 		if (!debugConfiguration.hasOwnProperty('dlvLoadConfig') && dlvConfig.hasOwnProperty('dlvLoadConfig')) {
 			debugConfiguration['dlvLoadConfig'] = dlvConfig['dlvLoadConfig'];
 		}
@@ -162,10 +188,7 @@
 		}
 		if (debugConfiguration['cwd']) {
 			// expand 'cwd' folder path containing '~', which would cause dlv to fail
-			debugConfiguration['cwd'] = resolvePath(debugConfiguration['cwd']);
-		}
-		if (!debugConfiguration.hasOwnProperty('debugAdapter') && dlvConfig.hasOwnProperty('debugAdapter')) {
-			debugConfiguration['debugAdapter'] = dlvConfig['debugAdapter'];
+			debugConfiguration['cwd'] = resolveHomeDir(debugConfiguration['cwd']);
 		}
 
 		// Remove any '--gcflags' entries and show a warning
@@ -190,7 +213,6 @@
 			}
 		}
 
-		const debugAdapter = debugConfiguration['debugAdapter'] === 'dlv-dap' ? 'dlv-dap' : 'dlv';
 		const dlvToolPath = getBinPath(debugAdapter);
 		if (!path.isAbsolute(dlvToolPath)) {
 			const tool = getTool(debugAdapter);
@@ -208,8 +230,19 @@
 		if (debugAdapter === 'dlv-dap' && !dlvDAPVersionCurrent) {
 			const tool = getToolAtVersion('dlv-dap');
 			if (await shouldUpdateTool(tool, dlvToolPath)) {
-				promptForUpdatingTool('dlv-dap');
-				return;
+				// If the user has opted in to automatic tool updates, we can update
+				// without prompting.
+				const toolsManagementConfig = getGoConfig()['toolsManagement'];
+				if (toolsManagementConfig && toolsManagementConfig['autoUpdate'] === true) {
+					const goVersion = await getGoVersion();
+					const toolVersion = { ...tool, version: tool.latestVersion }; // ToolWithVersion
+					await installTools([toolVersion], goVersion, true);
+				} else {
+					// If we are prompting the user to update, we do not want to continue
+					// with this debug session.
+					promptForUpdatingTool(tool.name);
+					return;
+				}
 			}
 			dlvDAPVersionCurrent = true;
 		}
@@ -220,8 +253,19 @@
 		}
 
 		if (debugConfiguration['mode'] === 'auto') {
-			debugConfiguration['mode'] =
-				activeEditor && activeEditor.document.fileName.endsWith('_test.go') ? 'test' : 'debug';
+			let filename = activeEditor?.document?.fileName;
+			if (debugConfiguration['program'] && debugConfiguration['program'].endsWith('.go')) {
+				// If the 'program' attribute is a file, not a directory, then we will determine the mode from that
+				// file path instead of the currently active file.
+				filename = debugConfiguration['program'];
+			}
+			debugConfiguration['mode'] = filename.endsWith('_test.go') ? 'test' : 'debug';
+		}
+
+		if (debugConfiguration['mode'] === 'test' && debugConfiguration['program'].endsWith('_test.go')) {
+			// Running a test file in file mode does not make sense, so change the program
+			// to the directory.
+			debugConfiguration['program'] = path.dirname(debugConfiguration['program']);
 		}
 
 		if (debugConfiguration.request === 'launch' && debugConfiguration['mode'] === 'remote') {
@@ -232,6 +276,7 @@
 		}
 
 		if (
+			debugAdapter !== 'dlv-dap' &&
 			debugConfiguration.request === 'attach' &&
 			debugConfiguration['mode'] === 'remote' &&
 			debugConfiguration['program']
diff --git a/src/goDebugFactory.ts b/src/goDebugFactory.ts
index 656de92..2e9b547 100644
--- a/src/goDebugFactory.ts
+++ b/src/goDebugFactory.ts
@@ -14,8 +14,11 @@
 import * as fs from 'fs';
 import * as net from 'net';
 import { getTool } from './goTools';
+import { Logger, TimestampedLogger } from './goLogging';
 
 export class GoDebugAdapterDescriptorFactory implements vscode.DebugAdapterDescriptorFactory {
+	constructor(private outputChannel?: vscode.OutputChannel) {}
+
 	public createDebugAdapterDescriptor(
 		session: vscode.DebugSession,
 		executable: vscode.DebugAdapterExecutable | undefined
@@ -30,19 +33,40 @@
 		console.log('GoDebugAdapterDescriptorFactory.dispose');
 	}
 
-	private createDebugAdapterDescriptorDlvDap(
+	private async createDebugAdapterDescriptorDlvDap(
 		configuration: vscode.DebugConfiguration
-	): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
+	): Promise<vscode.ProviderResult<vscode.DebugAdapterDescriptor>> {
 		if (configuration.port) {
 			return new vscode.DebugAdapterServer(configuration.port, configuration.host ?? '127.0.0.1');
 		}
-		const d = new DelveDAPOutputAdapter(configuration);
+		const logger = new TimestampedLogger(configuration.trace, this.outputChannel);
+		const d = new DelveDAPOutputAdapter(configuration, logger);
+		await d.startAndConnectToServer();
 		return new vscode.DebugAdapterInlineImplementation(d);
 	}
 }
 
-// TODO(hyangah): Code below needs refactoring to avoid using vscode API
-// so we can use from a separate debug adapter executable in testing.
+export class GoDebugAdapterTrackerFactory implements vscode.DebugAdapterTrackerFactory {
+	constructor(private outputChannel: vscode.OutputChannel) {}
+
+	createDebugAdapterTracker(session: vscode.DebugSession) {
+		const level = session.configuration?.trace;
+		if (!level || level === 'off') {
+			return null;
+		}
+		const logger = new TimestampedLogger(session.configuration?.trace || 'off', this.outputChannel);
+		return {
+			onWillStartSession: () =>
+				logger.debug(`session ${session.id} will start with ${JSON.stringify(session.configuration)}\n`),
+			onWillReceiveMessage: (message: any) => logger.trace(`client -> ${JSON.stringify(message)}\n`),
+			onDidSendMessage: (message: any) => logger.trace(`client <- ${JSON.stringify(message)}\n`),
+			onError: (error: Error) => logger.error(`error: ${error}\n`),
+			onWillStopSession: () => logger.debug(`session ${session.id} will stop\n`),
+			onExit: (code: number | undefined, signal: string | undefined) =>
+				logger.info(`debug adapter exited: (code: ${code}, signal: ${signal})\n`)
+		};
+	}
+}
 
 const TWO_CRLF = '\r\n\r\n';
 
@@ -54,8 +78,11 @@
 	// connection from/to server (= dlv dap)
 	private readable?: stream.Readable;
 	private writable?: stream.Writable;
+	protected logger?: Logger;
+	private terminated = false;
 
-	constructor() {
+	constructor(logger: Logger) {
+		this.logger = logger;
 		this.onDidSendMessage = this.messageEmitter.event;
 	}
 
@@ -79,13 +106,13 @@
 				'utf8',
 				(err) => {
 					if (err) {
-						console.log(`error sending message: ${err}`);
+						this.logger?.error(`error sending message: ${err}`);
 						this.sendMessageToClient(new TerminatedEvent());
 					}
 				}
 			);
 		} else {
-			console.log(`stream is closed; dropping ${json}`);
+			this.logger?.error(`stream is closed; dropping ${json}`);
 		}
 	}
 
@@ -102,9 +129,14 @@
 			this.readable = undefined;
 		});
 		this.readable.on('error', (err) => {
+			if (this.terminated) {
+				return;
+			}
+			this.terminated = true;
+
 			if (err) {
-				console.log(`stream error: ${err}`);
-				this.sendMessageToClient(new OutputEvent(`socket to network closed: ${err}`, 'console'));
+				this.logger?.error(`connection error: ${err}`);
+				this.sendMessageToClient(new OutputEvent(`connection error: ${err}\n`, 'console'));
 			}
 			this.sendMessageToClient(new TerminatedEvent());
 		});
@@ -160,75 +192,72 @@
 // VSCode and a dlv dap process spawned and managed by this adapter.
 // It turns the process's stdout/stderrr into OutputEvent.
 export class DelveDAPOutputAdapter extends ProxyDebugAdapter {
-	constructor(private config: vscode.DebugConfiguration, private outputToConsole?: boolean) {
-		super();
+	constructor(private config: vscode.DebugConfiguration, logger?: Logger) {
+		super(logger);
 	}
 
-	private connected: Promise<void>;
 	private dlvDapServer: ChildProcess;
 	private port: number;
 	private socket: net.Socket;
 	private terminatedOnError = false;
 
 	protected async sendMessageToServer(message: vscode.DebugProtocolMessage): Promise<void> {
-		if (!this.connected) {
-			this.connected = this.startAndConnectToServer();
-		}
-		try {
-			await this.connected;
-			super.sendMessageToServer(message);
-		} catch (err) {
-			if (this.terminatedOnError) {
-				return;
-			}
-			this.terminatedOnError = true;
-			// If there was an error connecting, show an error message
-			// and send a terminated event, since we cannot start.
-			if (err) {
-				const errMsg = `Debug Error: ${err}`;
-				this.outputEvent(errMsg, 'stderr');
-				vscode.window.showErrorMessage(errMsg);
-			}
-			this.sendMessageToClient(new TerminatedEvent());
-			return;
-		}
+		super.sendMessageToServer(message);
 	}
 
-	async dispose() {
+	async dispose(timeoutMS?: number) {
+		// NOTE: OutputEvents from here may not show up in DEBUG CONSOLE
+		// because the debug session is terminating.
 		await super.dispose();
-
-		if (this.connected === undefined) {
+		if (!this.dlvDapServer) {
 			return;
 		}
-		this.connected = undefined;
+
+		if (timeoutMS === undefined) {
+			timeoutMS = 1_000;
+		}
 		const dlvDapServer = this.dlvDapServer;
+		this.dlvDapServer = undefined;
 		if (!dlvDapServer) {
 			return;
 		}
 		if (dlvDapServer.exitCode !== null) {
-			console.log(`dlv dap process(${dlvDapServer.pid}) exited ${dlvDapServer.exitCode}`);
+			this.logger?.info(
+				`dlv dap process(${dlvDapServer.pid}) already exited (exit code: ${dlvDapServer.exitCode})`
+			);
 			return;
 		}
 		await new Promise<void>((resolve) => {
 			const exitTimeoutToken = setTimeout(() => {
-				console.log(`killing dlv dap process(${dlvDapServer.pid}) after 1sec`);
-				killProcessTree(dlvDapServer);
+				this.logger?.error(`dlv dap process (${dlvDapServer.pid}) isn't responding. Killing...`);
+				dlvDapServer.kill('SIGINT'); // Don't use treekill but let dlv handle cleaning up the child processes.
 				resolve();
-			}, 1_000);
-			dlvDapServer.on('exit', () => {
-				console.log(`dlv dap process(${dlvDapServer.pid}) exited`);
+			}, timeoutMS);
+			dlvDapServer.on('exit', (code, signal) => {
 				clearTimeout(exitTimeoutToken);
+				if (code || signal) {
+					this.logger?.error(
+						`dlv dap process(${dlvDapServer.pid}) exited (exit code: ${code} signal: ${signal})`
+					);
+				}
 				resolve();
 			});
 		});
 	}
 
-	private async startAndConnectToServer() {
+	public async startAndConnectToServer() {
 		const { port, host, dlvDapServer } = await startDapServer(
 			this.config,
 			(msg) => this.outputEvent('stdout', msg),
 			(msg) => this.outputEvent('stderr', msg),
-			(msg) => this.outputEvent('console', msg)
+			(msg) => {
+				this.outputEvent('console', msg);
+				// Some log messages generated after vscode stops the debug session
+				// may not appear in the DEBUG CONSOLE. For easier debugging, log
+				// the messages through the logger that prints to Go Debug output
+				// channel.
+				this.logger?.info(msg);
+			}
 		);
 		const socket = await new Promise<net.Socket>((resolve, reject) => {
 			// eslint-disable-next-line prefer-const
@@ -251,9 +280,6 @@
 
 	private outputEvent(dest: string, output: string, data?: any) {
 		this.sendMessageToClient(new OutputEvent(output, dest, data));
-		if (this.outputToConsole) {
-			console.log(output);
-		}
 	}
 }
 
@@ -322,17 +348,45 @@
 	if (launchArgs.logOutput) {
 		dlvArgs.push('--log-output=' + launchArgs.logOutput);
 	}
+
+	const onWindows = process.platform === 'win32';
+
+	if (!onWindows) {
+		dlvArgs.push('--log-dest=3');
+	}
+
+	const logDest = launchArgs.logDest;
+	if (typeof logDest === 'number') {
+		logErr('Using a file descriptor for `logDest` is not allowed.');
+		throw new Error('Using a file descriptor for `logDest` is not allowed.');
+	}
+	if (logDest && !path.isAbsolute(logDest)) {
+		logErr(
+			'Using a relative path for `logDest` is not allowed.\nSee [variables](https://code.visualstudio.com/docs/editor/variables-reference)'
+		);
+		throw new Error('Using a relative path for `logDest` is not allowed');
+	}
+	if (logDest && onWindows) {
+		logErr(
+			'Using `logDest` or `--log-dest` is not supported on windows yet. See https://github.com/golang/vscode-go/issues/1472.'
+		);
+		throw new Error('Using `logDest` on windows is not allowed');
+	}
+
+	const logDestStream = logDest ? fs.createWriteStream(logDest) : undefined;
+
 	logConsole(`Running: ${dlvPath} ${dlvArgs.join(' ')}\n`);
 
 	const dir = parseProgramArgSync(launchArgs).dirname;
 	// TODO(hyangah): determine the directories:
 	//    run `dlv` => where dlv will create the default __debug_bin. (This won't work if the directory is not writable. Fix it)
 	//    build program => 'program' directory. (This won't work for multimodule workspace. Fix it)
-	//    run program => cwd or wd (If test, make sure to run in the package directory.)
+	//    run program => cwd (If test, make sure to run in the package directory.)
 	return await new Promise<ChildProcess>((resolve, reject) => {
 		const p = spawn(dlvPath, dlvArgs, {
 			cwd: dir,
-			env
+			env,
+			stdio: ['pipe', 'pipe', 'pipe', 'pipe'] // --log-dest=3
 		});
 		let started = false;
 		const timeoutToken: NodeJS.Timer = setTimeout(
@@ -344,6 +398,7 @@
 			clearTimeout(timeoutToken);
 			started = true;
 			if (err) {
+				logConsole(`Failed to start 'dlv': ${err}\nKilling the dlv process...`);
 				killProcessTree(p); // We do not need to wait for p to actually be killed.
 				reject(new Error(err));
 			} else {
@@ -352,22 +407,15 @@
 		};
 
 		p.stdout.on('data', (chunk) => {
+			const msg = chunk.toString();
 			if (!started) {
-				// TODO(hyangah): when --log-dest is specified, the following message
-				// will be written to the log dest file, not stdout/stderr.
-				// Either disable --log-dest, or take advantage of it, i.e.,
-				// always pass a file descriptor to --log-dest, watch the file
-				// descriptor to process the log output, and also swap os.Stdout/os.Stderr
-				// in dlv dap for launch requests to generate proper OutputEvents.
-				if (chunk.toString().startsWith('DAP server listening at:')) {
+				if (msg.startsWith('DAP server listening at:')) {
 					stopWaitingForServerToStart();
 				} else {
-					stopWaitingForServerToStart(
-						`Expected 'DAP server listening at:' from debug adapter got '${chunk.toString()}'`
-					);
+					stopWaitingForServerToStart(`Unexpected output from dlv dap on start: '${msg}'`);
 				}
 			}
-			log(chunk.toString());
+			log(msg);
 		});
 		p.stderr.on('data', (chunk) => {
 			if (!started) {
@@ -375,13 +423,49 @@
 			}
 			logErr(chunk.toString());
 		});
-		p.on('close', (code) => {
-			// TODO: should we watch 'exit' instead?
+		p.stdio[3].on('data', (chunk) => {
+			const msg = chunk.toString();
 			if (!started) {
-				stopWaitingForServerToStart(`dlv dap closed with code: '${code}' signal: ${p.killed}`);
+				if (msg.startsWith('DAP server listening at:')) {
+					stopWaitingForServerToStart();
+				} else {
+					stopWaitingForServerToStart(`Expected 'DAP server listening at:' from debug adapter got '${msg}'`);
+				}
 			}
-			if (code) {
-				logErr(`Process exiting with code: ${code} signal: ${p.killed}`);
+			if (logDestStream) {
+				// always false on windows.
+				// write to the specified file.
+				logDestStream?.write(chunk, (err) => {
+					if (err) {
+						logConsole(`Error writing to ${logDest}: ${err}, log may be incomplete.`);
+					}
+				});
+			} else {
+				logConsole(msg);
+			}
+		});
+		p.stdio[3].on('close', () => {
+			// always false on windows.
+			logDestStream?.end();
+		});
+		p.on('close', (code, signal) => {
+			// TODO: should we watch 'exit' instead?
+
+			// NOTE: log messages here may not appear in DEBUG CONSOLE if the termination of
+			// the process was triggered by debug adapter's dispose when dlv dap doesn't
+			// respond to disconnect on time. In that case, it's possible that the session
+			// is in the middle of teardown and DEBUG CONSOLE isn't accessible. Check
+			// Go Debug output channel.
+			if (!started) {
+				stopWaitingForServerToStart(`dlv dap terminated with code: ${code} signal: ${signal}\n`);
+			}
+			if (typeof code === 'number') {
+				// The process exited on its own.
+				logConsole(`dlv dap (${p.pid}) exited with code: ${code}\n`);
+			} else if (code === null && signal) {
+				logConsole(`dlv dap (${p.pid}) was killed by signal: ${signal}\n`);
+			} else {
+				logConsole(`dlv dap (${p.pid}) terminated with code: ${code} signal: ${signal}\n`);
 			}
 		});
 		p.on('error', (err) => {
@@ -389,13 +473,13 @@
 				stopWaitingForServerToStart(`Unexpected error from dlv dap on start: '${err}'`);
 			}
 			if (err) {
-				logErr(`Error: ${err}`);
+				logConsole(`Error: ${err}\n`);
 			}
 		});
 	});
 }
 
-function parseProgramArgSync(
+export function parseProgramArgSync(
 	launchArgs: vscode.DebugConfiguration
 ): { program: string; dirname: string; programIsDirectory: boolean } {
 	const program = launchArgs.program;
diff --git a/src/goEnvironmentStatus.ts b/src/goEnvironmentStatus.ts
index 135eed1..1190b31 100644
--- a/src/goEnvironmentStatus.ts
+++ b/src/goEnvironmentStatus.ts
@@ -32,7 +32,7 @@
 	fixDriveCasingInWindows,
 	getBinPathFromEnvVar,
 	getCurrentGoRoot,
-	pathExists
+	dirExists
 } from './utils/pathUtils';
 import vscode = require('vscode');
 import WebRequest = require('web-request');
@@ -63,6 +63,22 @@
 const CLEAR_SELECTION = '$(clear-all) Clear selection';
 const CHOOSE_FROM_FILE_BROWSER = '$(folder) Choose from file browser';
 
+function canChooseGoEnvironment() {
+	// if there is no workspace, show GOROOT with message
+	if (!vscode.workspace.name) {
+		return { ok: false, reason: 'Switching Go version is not yet supported in single-file mode.' };
+	}
+
+	if (getGoConfig().get('goroot')) {
+		return { ok: false, reason: 'Switching Go version when "go.goroot" is set is unsupported.' };
+	}
+
+	if (process.env['GOROOT']) {
+		return { ok: false, reason: 'Switching Go version when process.env["GOROOT"] is set is unsupported.' };
+	}
+
+	return { ok: true };
+}
 /**
  * Present a command palette menu to the user to select their go binary
  */
@@ -70,12 +86,9 @@
 	if (!goEnvStatusbarItem) {
 		return;
 	}
-
-	// if there is no workspace, show GOROOT with message
-	if (!vscode.workspace.name) {
-		vscode.window.showInformationMessage(
-			`GOROOT: ${getCurrentGoRoot()}. Switching Go version is not yet supported in single-file mode.`
-		);
+	const { ok, reason } = canChooseGoEnvironment();
+	if (!ok) {
+		vscode.window.showInformationMessage(`GOROOT: ${getCurrentGoRoot()}. ${reason}`);
 		return;
 	}
 
@@ -265,7 +278,7 @@
 
 			outputChannel.appendLine('Finding newly downloaded Go');
 			const sdkPath = path.join(os.homedir(), 'sdk');
-			if (!(await pathExists(sdkPath))) {
+			if (!(await dirExists(sdkPath))) {
 				outputChannel.appendLine(`SDK path does not exist: ${sdkPath}`);
 				throw new Error(`SDK path does not exist: ${sdkPath}`);
 			}
@@ -343,9 +356,10 @@
 			for (const term of vscode.window.terminals) {
 				updateIntegratedTerminal(term);
 			}
-			if (!terminalCreationListener) {
-				terminalCreationListener = vscode.window.onDidOpenTerminal(updateIntegratedTerminal);
+			if (terminalCreationListener) {
+				terminalCreationListener.dispose();
 			}
+			terminalCreationListener = vscode.window.onDidOpenTerminal(updateIntegratedTerminal);
 		} else {
 			environmentVariableCollection?.prepend(pathEnvVar, newGoRuntimeBase + path.delimiter);
 		}
@@ -428,8 +442,8 @@
 	const versionStr = version.format(true);
 	const versionWords = versionStr.split(' ');
 	if (versionWords.length > 1 && versionWords[0] === 'devel') {
-		// Go devel +hash
-		return `Go ${versionWords[1]}`;
+		// go devel +hash or go devel go1.17-hash
+		return versionWords[1].startsWith('go') ? `Go ${versionWords[1].slice(2)}` : `Go ${versionWords[1]}`;
 	} else {
 		return `Go ${versionWords[0]}`;
 	}
@@ -439,7 +453,7 @@
 	// get list of Go versions
 	const sdkPath = path.join(os.homedir(), 'sdk');
 
-	if (!(await pathExists(sdkPath))) {
+	if (!(await dirExists(sdkPath))) {
 		return [];
 	}
 	const readdir = promisify(fs.readdir);
diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts
index 4cc02f9..a94aff9 100644
--- a/src/goInstallTools.ts
+++ b/src/goInstallTools.ts
@@ -412,6 +412,9 @@
 	if (toolName === 'gopls') {
 		choices = ['Always Update', 'Update Once', 'Release Notes'];
 	}
+	if (toolName === 'dlv-dap') {
+		choices = ['Always Update', 'Update Once'];
+	}
 
 	const goVersion = await getGoVersion();
 
diff --git a/src/goLanguageServer.ts b/src/goLanguageServer.ts
index ddbaef6..a2b2d88 100644
--- a/src/goLanguageServer.ts
+++ b/src/goLanguageServer.ts
@@ -59,7 +59,7 @@
 import { GoWorkspaceSymbolProvider } from './goSymbol';
 import { getTool, Tool } from './goTools';
 import { GoTypeDefinitionProvider } from './goTypeDefinition';
-import { getFromGlobalState, getFromWorkspaceState, updateGlobalState, updateWorkspaceState } from './stateUtils';
+import { getFromGlobalState, updateGlobalState, updateWorkspaceState } from './stateUtils';
 import {
 	getBinPath,
 	getCheckForToolsUpdatesConfig,
@@ -73,6 +73,7 @@
 import WebRequest = require('web-request');
 import { FoldingContext } from 'vscode';
 import { ProvideFoldingRangeSignature } from 'vscode-languageclient/lib/common/foldingRange';
+import { daysBetween, getStateConfig, maybePromptForSurvey, timeDay, timeMinute } from './goSurvey';
 
 export interface LanguageServerConfig {
 	serverName: string;
@@ -116,7 +117,7 @@
 
 // lastUserAction is the time of the last user-triggered change.
 // A user-triggered change is a didOpen, didChange, didSave, or didClose event.
-let lastUserAction: Date = new Date();
+export let lastUserAction: Date = new Date();
 
 // startLanguageServerWithFallback starts the language server, if enabled,
 // or falls back to the default language providers.
@@ -134,6 +135,15 @@
 				return;
 		}
 	}
+	const schemes = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.scheme);
+	if (schemes?.length > 0 && !schemes.includes('file') && !schemes.includes('untitled')) {
+		outputChannel.appendLine(
+			`None of the folders in this workspace ${schemes.join(
+				','
+			)} are the types the language server recognizes. Disabling the language features.`
+		);
+		return;
+	}
 
 	const goConfig = getGoConfig();
 	const cfg = buildLanguageServerConfig(goConfig);
@@ -226,11 +236,17 @@
 	const survey = async () => {
 		setTimeout(survey, timeDay);
 
-		const cfg = buildLanguageServerConfig(getGoConfig());
-		if (!usingGopls(cfg)) {
+		// Only prompt for the survey if the user is working on Go code.
+		let foundGo = false;
+		for (const doc of vscode.workspace.textDocuments) {
+			if (doc.languageId === 'go') {
+				foundGo = true;
+			}
+		}
+		if (!foundGo) {
 			return;
 		}
-		maybePromptForGoplsSurvey();
+		maybePromptForSurvey();
 	};
 	setTimeout(update, 10 * timeMinute);
 	setTimeout(survey, 30 * timeMinute);
@@ -1260,225 +1276,6 @@
 	return null;
 }
 
-// SurveyConfig is the set of global properties used to determine if
-// we should prompt a user to take the gopls survey.
-export interface SurveyConfig {
-	// prompt is true if the user can be prompted to take the survey.
-	// It is false if the user has responded "Never" to the prompt.
-	prompt?: boolean;
-
-	// promptThisMonth is true if we have used a random number generator
-	// to determine if the user should be prompted this month.
-	// It is undefined if we have not yet made the determination.
-	promptThisMonth?: boolean;
-
-	// dateToPromptThisMonth is the date on which we should prompt the user
-	// this month.
-	dateToPromptThisMonth?: Date;
-
-	// dateComputedPromptThisMonth is the date on which the values of
-	// promptThisMonth and dateToPromptThisMonth were set.
-	dateComputedPromptThisMonth?: Date;
-
-	// lastDatePrompted is the most recent date that the user has been prompted.
-	lastDatePrompted?: Date;
-
-	// lastDateAccepted is the most recent date that the user responded "Yes"
-	// to the survey prompt. The user need not have completed the survey.
-	lastDateAccepted?: Date;
-}
-
-function maybePromptForGoplsSurvey() {
-	const now = new Date();
-	let cfg = shouldPromptForGoplsSurvey(now, getSurveyConfig());
-	if (!cfg) {
-		return;
-	}
-	flushSurveyConfig(cfg);
-	if (!cfg.dateToPromptThisMonth) {
-		return;
-	}
-	const callback = async () => {
-		const currentTime = new Date();
-
-		// Make sure the user has been idle for at least a minute.
-		if (minutesBetween(lastUserAction, currentTime) < 1) {
-			setTimeout(callback, 5 * timeMinute);
-			return;
-		}
-		cfg = await promptForSurvey(cfg, now);
-		if (cfg) {
-			flushSurveyConfig(cfg);
-		}
-	};
-	const ms = msBetween(now, cfg.dateToPromptThisMonth);
-	setTimeout(callback, ms);
-}
-
-export function shouldPromptForGoplsSurvey(now: Date, cfg: SurveyConfig): SurveyConfig {
-	// If the prompt value is not set, assume we haven't prompted the user
-	// and should do so.
-	if (cfg.prompt === undefined) {
-		cfg.prompt = true;
-	}
-	if (!cfg.prompt) {
-		return;
-	}
-
-	// Check if the user has taken the survey in the last year.
-	// Don't prompt them if they have been.
-	if (cfg.lastDateAccepted) {
-		if (daysBetween(now, cfg.lastDateAccepted) < 365) {
-			return;
-		}
-	}
-
-	// Check if the user has been prompted for the survey in the last 90 days.
-	// Don't prompt them if they have been.
-	if (cfg.lastDatePrompted) {
-		if (daysBetween(now, cfg.lastDatePrompted) < 90) {
-			return;
-		}
-	}
-
-	// Check if the extension has been activated this month.
-	if (cfg.dateComputedPromptThisMonth) {
-		// The extension has been activated this month, so we should have already
-		// decided if the user should be prompted.
-		if (daysBetween(now, cfg.dateComputedPromptThisMonth) < 30) {
-			return cfg;
-		}
-	}
-	// This is the first activation this month (or ever), so decide if we
-	// should prompt the user. This is done by generating a random number in
-	// the range [0, 1) and checking if it is < probability, which varies
-	// depending on whether the default or the nightly is in use.
-	// We then randomly pick a day in the rest of the month on which to prompt
-	// the user.
-	let probability = 0.01; // lower probability for the regular extension
-	if (isInPreviewMode()) {
-		probability = 0.0275;
-	}
-	cfg.promptThisMonth = Math.random() < probability;
-	if (cfg.promptThisMonth) {
-		// end is the last day of the month, day is the random day of the
-		// month on which to prompt.
-		const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
-		const day = randomIntInRange(now.getUTCDate(), end.getUTCDate());
-		cfg.dateToPromptThisMonth = new Date(now.getFullYear(), now.getMonth(), day);
-	} else {
-		cfg.dateToPromptThisMonth = undefined;
-	}
-	cfg.dateComputedPromptThisMonth = now;
-	return cfg;
-}
-
-async function promptForSurvey(cfg: SurveyConfig, now: Date): Promise<SurveyConfig> {
-	const selected = await vscode.window.showInformationMessage(
-		`Looks like you're using gopls, the Go language server.
-Would you be willing to fill out a quick survey about your experience with gopls?`,
-		'Yes',
-		'Not now',
-		'Never'
-	);
-
-	// Update the time last asked.
-	cfg.lastDatePrompted = now;
-
-	switch (selected) {
-		case 'Yes':
-			{
-				cfg.lastDateAccepted = now;
-				cfg.prompt = true;
-				const usersGoplsVersion = await getLocalGoplsVersion(latestConfig);
-				await vscode.env.openExternal(
-					vscode.Uri.parse(
-						`https://google.qualtrics.com/jfe/form/SV_ekAdHVcVcvKUojX?gopls=${usersGoplsVersion}&extid=${extensionId}`
-					)
-				);
-			}
-			break;
-		case 'Not now':
-			cfg.prompt = true;
-
-			vscode.window.showInformationMessage("No problem! We'll ask you again another time.");
-			break;
-		case 'Never':
-			cfg.prompt = false;
-
-			vscode.window.showInformationMessage("No problem! We won't ask again.");
-			break;
-		default:
-			// If the user closes the prompt without making a selection, treat it
-			// like a "Not now" response.
-			cfg.prompt = true;
-
-			break;
-	}
-	return cfg;
-}
-
-export const goplsSurveyConfig = 'goplsSurveyConfig';
-
-function getSurveyConfig(): SurveyConfig {
-	return getStateConfig(goplsSurveyConfig) as SurveyConfig;
-}
-
-export function resetSurveyConfig() {
-	flushSurveyConfig(null);
-}
-
-function flushSurveyConfig(cfg: SurveyConfig) {
-	if (cfg) {
-		updateGlobalState(goplsSurveyConfig, JSON.stringify(cfg));
-	} else {
-		updateGlobalState(goplsSurveyConfig, null); // reset
-	}
-}
-
-function getStateConfig(globalStateKey: string, workspace?: boolean): any {
-	let saved: any;
-	if (workspace === true) {
-		saved = getFromWorkspaceState(globalStateKey);
-	} else {
-		saved = getFromGlobalState(globalStateKey);
-	}
-	if (saved === undefined) {
-		return {};
-	}
-	try {
-		const cfg = JSON.parse(saved, (key: string, value: any) => {
-			// Make sure values that should be dates are correctly converted.
-			if (key.toLowerCase().includes('date') || key.toLowerCase().includes('timestamp')) {
-				return new Date(value);
-			}
-			return value;
-		});
-		return cfg || {};
-	} catch (err) {
-		console.log(`Error parsing JSON from ${saved}: ${err}`);
-		return {};
-	}
-}
-
-export async function showSurveyConfig() {
-	outputChannel.appendLine('Gopls Survey Configuration');
-	outputChannel.appendLine(JSON.stringify(getSurveyConfig(), null, 2));
-	outputChannel.show();
-
-	const selected = await vscode.window.showInformationMessage('Prompt for survey?', 'Yes', 'Maybe', 'No');
-	switch (selected) {
-		case 'Yes':
-			promptForSurvey(getSurveyConfig(), new Date());
-			break;
-		case 'Maybe':
-			maybePromptForGoplsSurvey();
-			break;
-		default:
-			break;
-	}
-}
-
 // errorKind refers to the different possible kinds of gopls errors.
 enum errorKind {
 	initializationFailure,
@@ -1638,31 +1435,6 @@
 	}
 }
 
-// randomIntInRange returns a random integer between min and max, inclusive.
-function randomIntInRange(min: number, max: number): number {
-	const low = Math.ceil(min);
-	const high = Math.floor(max);
-	return Math.floor(Math.random() * (high - low + 1)) + low;
-}
-
-export const timeMinute = 1000 * 60;
-const timeHour = timeMinute * 60;
-const timeDay = timeHour * 24;
-
-// daysBetween returns the number of days between a and b.
-function daysBetween(a: Date, b: Date): number {
-	return msBetween(a, b) / timeDay;
-}
-
-// minutesBetween returns the number of minutes between a and b.
-function minutesBetween(a: Date, b: Date): number {
-	return msBetween(a, b) / timeMinute;
-}
-
-function msBetween(a: Date, b: Date): number {
-	return Math.abs(a.getTime() - b.getTime());
-}
-
 export function showServerOutputChannel() {
 	if (!languageServerIsRunning) {
 		vscode.window.showInformationMessage('gopls is not running');
diff --git a/src/goLogging.ts b/src/goLogging.ts
index 3c30247..fb1bc9e 100644
--- a/src/goLogging.ts
+++ b/src/goLogging.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
 /*---------------------------------------------------------
  * Copyright 2020 The Go Authors. All rights reserved.
  * Licensed under the MIT License. See LICENSE in the project root for license information.
@@ -6,76 +5,113 @@
 
 'use strict';
 
-// Our log level.
-enum LogLevel {
-	Off = 100,
-	Error = 50,
-	Info = 30,
-	Verbose = 20
-	// TODO: Trace, Warn level
-}
+type LogLevel = 'off' | 'error' | 'warn' | 'info' | 'trace' | 'verbose';
 
-let currentLogLevel: LogLevel = LogLevel.Error;
-
-const levelMap: { [k: string]: LogLevel } = {
-	off: LogLevel.Off,
-	error: LogLevel.Error,
-	info: LogLevel.Info,
-	verbose: LogLevel.Verbose
+const levels: { [key in LogLevel]: number } = {
+	off: -1,
+	error: 0,
+	warn: 1,
+	info: 2,
+	trace: 3,
+	verbose: 4
 };
 
-function levelPrefix(l: LogLevel): string {
-	switch (l) {
-		case LogLevel.Off:
-			return 'Go[O]:';
-		case LogLevel.Error:
-			return 'Go[E]:';
-		case LogLevel.Info:
-			return 'Go[I]:';
-		case LogLevel.Verbose:
-			return 'Go[V]:';
-		default:
-			return 'Go[?]:';
+function levelToString(level: number) {
+	switch (level) {
+		case levels.error:
+			return 'Error';
+		case levels.warn:
+			return 'Warn';
+		case levels.info:
+			return 'Info';
+		case levels.trace:
+			return 'Trace';
+		case levels.verbose:
+			return 'Verbose';
+	}
+	return '';
+}
+
+interface outputChannelType {
+	appendLine: (msg: string) => void;
+}
+// Logger outputs messages of the specified log levels to the vscode output channel or console.
+export class Logger {
+	protected minLevel: number;
+
+	constructor(levelName: LogLevel, private outputChannel?: outputChannelType, private logToConsole?: boolean) {
+		this.minLevel = levels[levelName] || levels.error;
+	}
+
+	protected log(msgLevel: number, msg: string) {
+		if (this.minLevel < 0) {
+			return; // logging is off.
+		}
+		if (this.minLevel < msgLevel) {
+			return;
+		}
+		this.outputChannel?.appendLine(msg);
+		if (this.logToConsole) console.log(msg);
+	}
+
+	error(msg: string) {
+		this.log(levels.error, msg);
+	}
+	warn(msg: string) {
+		this.log(levels.warn, msg);
+	}
+	info(msg: string) {
+		this.log(levels.info, msg);
+	}
+	trace(msg: string) {
+		this.log(levels.trace, msg);
+	}
+	debug(msg: string) {
+		this.log(levels.verbose, msg);
+	}
+}
+
+// TimestampedLogger is a logger that prepends the timestamp to every log message.
+export class TimestampedLogger extends Logger {
+	log(msgLevel: number, msg: string) {
+		const ts = new Date();
+		const hhmmss = ts.toLocaleTimeString([], {
+			hour: '2-digit',
+			minute: '2-digit',
+			second: '2-digit',
+			hour12: false
+		});
+		const msec = ts.getMilliseconds();
+		super.log(msgLevel, `[${levelToString(msgLevel)} - ${hhmmss}.${msec}] ${msg}`);
 	}
 }
 
 export interface LogConfig {
-	level: string;
+	level: LogLevel;
 }
 
+let defaultLogger: Logger;
+
 export function setLogConfig(cfg: LogConfig) {
-	const logLevel = cfg?.level || 'error';
-	const l = levelMap[logLevel];
-	if (l) {
-		currentLogLevel = l;
-		return;
-	}
-	logError(`setLogLevel requested with invalid log level ${logLevel}, ignoring...`);
+	defaultLogger = new Logger(cfg.level);
 }
 
-// tslint:disable-next-line:no-any
-function log(logLevel: LogLevel, ...args: any[]) {
-	if (logLevel < currentLogLevel) {
-		return;
-	}
-	const p = levelPrefix(logLevel);
-	const a = Array.from(args);
-	a.unshift(p);
-	console.log(...a);
-	// TODO: support logging in vscode output channel.
+export function logError(msg: string) {
+	defaultLogger?.error(msg);
 }
 
-// tslint:disable-next-line:no-any
-export function logVerbose(...args: any[]) {
-	log(LogLevel.Verbose, ...args);
+export function logWarn(msg: string) {
+	defaultLogger?.warn(msg);
 }
 
-// tslint:disable-next-line:no-any
-export function logError(...args: any[]) {
-	log(LogLevel.Error, ...args);
+export function logInfo(msg: string) {
+	defaultLogger?.info(msg);
 }
 
-// tslint:disable-next-line:no-any
-export function logInfo(...args: any[]) {
-	log(LogLevel.Info, ...args);
+export function logTrace(msg: string) {
+	defaultLogger?.trace(msg);
+}
+
+export function logVerbose(msg: string) {
+	defaultLogger?.debug(msg);
 }
diff --git a/src/goMain.ts b/src/goMain.ts
index 3cf4872..28e4956 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -23,7 +23,7 @@
 	updateCodeCoverageDecorators
 } from './goCover';
 import { GoDebugConfigurationProvider } from './goDebugConfiguration';
-import { GoDebugAdapterDescriptorFactory } from './goDebugFactory';
+import { GoDebugAdapterDescriptorFactory, GoDebugAdapterTrackerFactory } from './goDebugFactory';
 import { extractFunction, extractVariable } from './goDoctor';
 import { toolExecutionEnvironment } from './goEnv';
 import {
@@ -47,23 +47,21 @@
 import {
 	isInPreviewMode,
 	languageServerIsRunning,
-	resetSurveyConfig,
 	showServerOutputChannel,
-	showSurveyConfig,
 	startLanguageServerWithFallback,
-	timeMinute,
 	watchLanguageServerConfiguration
 } from './goLanguageServer';
 import { lintCode } from './goLint';
 import { logVerbose, setLogConfig } from './goLogging';
 import { GO_MODE } from './goMode';
 import { addTags, removeTags } from './goModifytags';
-import { GO111MODULE, isModSupported } from './goModules';
+import { GO111MODULE, goModInit, isModSupported } from './goModules';
 import { playgroundCommand } from './goPlayground';
 import { GoReferencesCodeLensProvider } from './goReferencesCodelens';
 import { GoRunTestCodeLensProvider } from './goRunTestCodelens';
 import { disposeGoStatusBar, expandGoStatusBar, outputChannel, updateGoStatusBar } from './goStatus';
 import {
+	debugPrevious,
 	subTestAtCursor,
 	testAtCursor,
 	testCurrentFile,
@@ -98,11 +96,12 @@
 	isGoPathSet,
 	resolvePath
 } from './util';
-import { clearCacheForTools, fileExists, getCurrentGoRoot, setCurrentGoRoot } from './utils/pathUtils';
+import { clearCacheForTools, fileExists, getCurrentGoRoot, dirExists, setCurrentGoRoot } from './utils/pathUtils';
 import { WelcomePanel } from './welcome';
 import semver = require('semver');
 import vscode = require('vscode');
 import { getFormatTool } from './goFormat';
+import { resetSurveyConfig, showSurveyConfig, timeMinute } from './goSurvey';
 
 export let buildDiagnosticCollection: vscode.DiagnosticCollection;
 export let lintDiagnosticCollection: vscode.DiagnosticCollection;
@@ -152,8 +151,11 @@
 
 	const configGOROOT = getGoConfig()['goroot'];
 	if (configGOROOT) {
-		logVerbose(`go.goroot = '${configGOROOT}'`);
-		setCurrentGoRoot(resolvePath(configGOROOT));
+		const goroot = resolvePath(configGOROOT);
+		if (dirExists(goroot)) {
+			logVerbose(`setting GOROOT = ${goroot} because "go.goroot": "${configGOROOT}"`);
+			process.env['GOROOT'] = goroot;
+		}
 	}
 
 	// Present a warning about the deprecation of the go.documentLink setting.
@@ -233,12 +235,21 @@
 		)
 	);
 
-	const factory = new GoDebugAdapterDescriptorFactory();
+	const debugOutputChannel = vscode.window.createOutputChannel('Go Debug');
+	ctx.subscriptions.push(debugOutputChannel);
+
+	const factory = new GoDebugAdapterDescriptorFactory(debugOutputChannel);
 	ctx.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('go', factory));
 	if ('dispose' in factory) {
 		ctx.subscriptions.push(factory);
 	}
 
+	const tracker = new GoDebugAdapterTrackerFactory(debugOutputChannel);
+	ctx.subscriptions.push(vscode.debug.registerDebugAdapterTrackerFactory('go', tracker));
+	if ('dispose' in tracker) {
+		ctx.subscriptions.push(tracker);
+	}
+
 	buildDiagnosticCollection = vscode.languages.createDiagnosticCollection('go');
 	ctx.subscriptions.push(buildDiagnosticCollection);
 	lintDiagnosticCollection = vscode.languages.createDiagnosticCollection(
@@ -376,6 +387,12 @@
 	);
 
 	ctx.subscriptions.push(
+		vscode.commands.registerCommand('go.debug.previous', () => {
+			debugPrevious();
+		})
+	);
+
+	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.test.coverage', () => {
 			toggleCoverageCurrentPackage();
 		})
@@ -429,6 +446,17 @@
 			}
 			const updatedGoConfig = getGoConfig();
 
+			if (e.affectsConfiguration('go.goroot')) {
+				const configGOROOT = updatedGoConfig['goroot'];
+				if (configGOROOT) {
+					const goroot = resolvePath(configGOROOT);
+					const oldGoroot = process.env['GOROOT'];
+					if (oldGoroot !== goroot && dirExists(goroot)) {
+						logVerbose(`setting GOROOT = ${goroot} because "go.goroot": "${configGOROOT}"`);
+						process.env['GOROOT'] = goroot;
+					}
+				}
+			}
 			if (
 				e.affectsConfiguration('go.goroot') ||
 				e.affectsConfiguration('go.alternateTools') ||
@@ -570,6 +598,8 @@
 
 	ctx.subscriptions.push(vscode.commands.registerCommand('go.install.package', installCurrentPackage));
 
+	ctx.subscriptions.push(vscode.commands.registerCommand('go.run.modinit', goModInit));
+
 	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.extractServerChannel', () => {
 			showServerOutputChannel();
diff --git a/src/goModules.ts b/src/goModules.ts
index 7cbd939..341d041 100644
--- a/src/goModules.ts
+++ b/src/goModules.ts
@@ -6,16 +6,17 @@
 
 import cp = require('child_process');
 import path = require('path');
+import util = require('util');
 import vscode = require('vscode');
 import { getGoConfig } from './config';
 import { toolExecutionEnvironment } from './goEnv';
 import { getFormatTool } from './goFormat';
 import { installTools } from './goInstallTools';
+import { outputChannel } from './goStatus';
 import { getTool } from './goTools';
 import { getFromGlobalState, updateGlobalState } from './stateUtils';
-import { getBinPath, getGoVersion, getModuleCache } from './util';
+import { getBinPath, getGoVersion, getModuleCache, getWorkspaceFolderPath } from './util';
 import { envPath, fixDriveCasingInWindows, getCurrentGoRoot } from './utils/pathUtils';
-
 export let GO111MODULE: string;
 
 async function runGoModEnv(folderPath: string): Promise<string> {
@@ -184,3 +185,25 @@
 		});
 	});
 }
+
+export async function goModInit() {
+	outputChannel.clear();
+
+	const moduleName = await vscode.window.showInputBox({
+		prompt: 'Enter module name',
+		value: '',
+		placeHolder: 'example.com/m'
+	});
+
+	const goRuntimePath = getBinPath('go');
+	const execFile = util.promisify(cp.execFile);
+	try {
+		const env = toolExecutionEnvironment();
+		const cwd = getWorkspaceFolderPath();
+		await execFile(goRuntimePath, ['mod', 'init', moduleName], { env, cwd });
+	} catch (e) {
+		outputChannel.appendLine(e);
+		outputChannel.show();
+		vscode.window.showErrorMessage(`Error running 'go mod init ${moduleName}': See Go output channel for details`);
+	}
+}
diff --git a/src/goSurvey.ts b/src/goSurvey.ts
new file mode 100644
index 0000000..eb02760
--- /dev/null
+++ b/src/goSurvey.ts
@@ -0,0 +1,258 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/*---------------------------------------------------------
+ * Copyright 2021 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+'use strict';
+
+import vscode = require('vscode');
+import { getLocalGoplsVersion, isInPreviewMode, lastUserAction, latestConfig } from './goLanguageServer';
+import { outputChannel } from './goStatus';
+import { extensionId } from './const';
+import { getFromGlobalState, getFromWorkspaceState, updateGlobalState } from './stateUtils';
+
+// SurveyConfig is the set of global properties used to determine if
+// we should prompt a user to take the gopls survey.
+export interface SurveyConfig {
+	// prompt is true if the user can be prompted to take the survey.
+	// It is false if the user has responded "Never" to the prompt.
+	prompt?: boolean;
+
+	// promptThisMonth is true if we have used a random number generator
+	// to determine if the user should be prompted this month.
+	// It is undefined if we have not yet made the determination.
+	promptThisMonth?: boolean;
+
+	// dateToPromptThisMonth is the date on which we should prompt the user
+	// this month.
+	dateToPromptThisMonth?: Date;
+
+	// dateComputedPromptThisMonth is the date on which the values of
+	// promptThisMonth and dateToPromptThisMonth were set.
+	dateComputedPromptThisMonth?: Date;
+
+	// lastDatePrompted is the most recent date that the user has been prompted.
+	lastDatePrompted?: Date;
+
+	// lastDateAccepted is the most recent date that the user responded "Yes"
+	// to the survey prompt. The user need not have completed the survey.
+	lastDateAccepted?: Date;
+}
+
+export function maybePromptForSurvey() {
+	const now = new Date();
+	let cfg = shouldPromptForSurvey(now, getSurveyConfig());
+	if (!cfg) {
+		return;
+	}
+	flushSurveyConfig(cfg);
+	if (!cfg.dateToPromptThisMonth) {
+		return;
+	}
+	const callback = async () => {
+		const currentTime = new Date();
+
+		// Make sure the user has been idle for at least a minute.
+		if (minutesBetween(lastUserAction, currentTime) < 1) {
+			setTimeout(callback, 5 * timeMinute);
+			return;
+		}
+		cfg = await promptForSurvey(cfg, now);
+		if (cfg) {
+			flushSurveyConfig(cfg);
+		}
+	};
+	const ms = msBetween(now, cfg.dateToPromptThisMonth);
+	setTimeout(callback, ms);
+}
+
+export function shouldPromptForSurvey(now: Date, cfg: SurveyConfig): SurveyConfig {
+	// If the prompt value is not set, assume we haven't prompted the user
+	// and should do so.
+	if (cfg.prompt === undefined) {
+		cfg.prompt = true;
+	}
+	if (!cfg.prompt) {
+		return;
+	}
+
+	// Check if the user has taken the survey in the last year.
+	// Don't prompt them if they have been.
+	if (cfg.lastDateAccepted) {
+		if (daysBetween(now, cfg.lastDateAccepted) < 365) {
+			return;
+		}
+	}
+
+	// Check if the user has been prompted for the survey in the last 90 days.
+	// Don't prompt them if they have been.
+	if (cfg.lastDatePrompted) {
+		if (daysBetween(now, cfg.lastDatePrompted) < 90) {
+			return;
+		}
+	}
+
+	// Check if the extension has been activated this month.
+	if (cfg.dateComputedPromptThisMonth) {
+		// The extension has been activated this month, so we should have already
+		// decided if the user should be prompted.
+		if (daysBetween(now, cfg.dateComputedPromptThisMonth) < 30) {
+			return cfg;
+		}
+	}
+	// This is the first activation this month (or ever), so decide if we
+	// should prompt the user. This is done by generating a random number in
+	// the range [0, 1) and checking if it is < probability, which varies
+	// depending on whether the default or the nightly is in use.
+	// We then randomly pick a day in the rest of the month on which to prompt
+	// the user.
+	let probability = 0.01; // lower probability for the regular extension
+	if (isInPreviewMode()) {
+		probability = 0.0275;
+	}
+	cfg.promptThisMonth = Math.random() < probability;
+	if (cfg.promptThisMonth) {
+		// end is the last day of the month, day is the random day of the
+		// month on which to prompt.
+		const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
+		const day = randomIntInRange(now.getUTCDate(), end.getUTCDate());
+		cfg.dateToPromptThisMonth = new Date(now.getFullYear(), now.getMonth(), day);
+	} else {
+		cfg.dateToPromptThisMonth = undefined;
+	}
+	cfg.dateComputedPromptThisMonth = now;
+	return cfg;
+}
+
+// randomIntInRange returns a random integer between min and max, inclusive.
+function randomIntInRange(min: number, max: number): number {
+	const low = Math.ceil(min);
+	const high = Math.floor(max);
+	return Math.floor(Math.random() * (high - low + 1)) + low;
+}
+
+async function promptForSurvey(cfg: SurveyConfig, now: Date): Promise<SurveyConfig> {
+	const selected = await vscode.window.showInformationMessage(
+		`Looks like you are using the Go extension for VS Code.
+Could you help us improve this extension by filling out a 1-2 minute survey about your experience with it?`,
+		'Yes',
+		'Not now',
+		'Never'
+	);
+
+	// Update the time last asked.
+	cfg.lastDatePrompted = now;
+
+	switch (selected) {
+		case 'Yes':
+			{
+				cfg.lastDateAccepted = now;
+				cfg.prompt = true;
+				const goplsEnabled = latestConfig.enabled;
+				const usersGoplsVersion = await getLocalGoplsVersion(latestConfig);
+				await vscode.env.openExternal(
+					vscode.Uri.parse(
+						`https://google.qualtrics.com/jfe/form/SV_ekAdHVcVcvKUojX?usingGopls=${goplsEnabled}&gopls=${usersGoplsVersion}&extid=${extensionId}`
+					)
+				);
+			}
+			break;
+		case 'Not now':
+			cfg.prompt = true;
+
+			vscode.window.showInformationMessage("No problem! We'll ask you again another time.");
+			break;
+		case 'Never':
+			cfg.prompt = false;
+
+			vscode.window.showInformationMessage("No problem! We won't ask again.");
+			break;
+		default:
+			// If the user closes the prompt without making a selection, treat it
+			// like a "Not now" response.
+			cfg.prompt = true;
+
+			break;
+	}
+	return cfg;
+}
+
+export const goplsSurveyConfig = 'goplsSurveyConfig';
+
+function getSurveyConfig(): SurveyConfig {
+	return getStateConfig(goplsSurveyConfig) as SurveyConfig;
+}
+
+export function resetSurveyConfig() {
+	flushSurveyConfig(null);
+}
+
+function flushSurveyConfig(cfg: SurveyConfig) {
+	if (cfg) {
+		updateGlobalState(goplsSurveyConfig, JSON.stringify(cfg));
+	} else {
+		updateGlobalState(goplsSurveyConfig, null); // reset
+	}
+}
+
+export function getStateConfig(globalStateKey: string, workspace?: boolean): any {
+	let saved: any;
+	if (workspace === true) {
+		saved = getFromWorkspaceState(globalStateKey);
+	} else {
+		saved = getFromGlobalState(globalStateKey);
+	}
+	if (saved === undefined) {
+		return {};
+	}
+	try {
+		const cfg = JSON.parse(saved, (key: string, value: any) => {
+			// Make sure values that should be dates are correctly converted.
+			if (key.toLowerCase().includes('date') || key.toLowerCase().includes('timestamp')) {
+				return new Date(value);
+			}
+			return value;
+		});
+		return cfg || {};
+	} catch (err) {
+		console.log(`Error parsing JSON from ${saved}: ${err}`);
+		return {};
+	}
+}
+
+export async function showSurveyConfig() {
+	outputChannel.appendLine('Gopls Survey Configuration');
+	outputChannel.appendLine(JSON.stringify(getSurveyConfig(), null, 2));
+	outputChannel.show();
+
+	const selected = await vscode.window.showInformationMessage('Prompt for survey?', 'Yes', 'Maybe', 'No');
+	switch (selected) {
+		case 'Yes':
+			promptForSurvey(getSurveyConfig(), new Date());
+			break;
+		case 'Maybe':
+			maybePromptForSurvey();
+			break;
+		default:
+			break;
+	}
+}
+
+export const timeMinute = 1000 * 60;
+const timeHour = timeMinute * 60;
+export const timeDay = timeHour * 24;
+
+// daysBetween returns the number of days between a and b.
+export function daysBetween(a: Date, b: Date): number {
+	return msBetween(a, b) / timeDay;
+}
+
+// minutesBetween returns the number of minutes between a and b.
+function minutesBetween(a: Date, b: Date): number {
+	return msBetween(a, b) / timeMinute;
+}
+
+export function msBetween(a: Date, b: Date): number {
+	return Math.abs(a.getTime() - b.getTime());
+}
diff --git a/src/goTest.ts b/src/goTest.ts
index d1cb29f..e25c9d7 100644
--- a/src/goTest.ts
+++ b/src/goTest.ts
@@ -24,6 +24,11 @@
 // the last test to be easily re-executed.
 let lastTestConfig: TestConfig;
 
+// lastDebugConfig holds a reference to the last executed DebugConfiguration which allows
+// the last test to be easily re-executed and debugged.
+let lastDebugConfig: vscode.DebugConfiguration;
+let lastDebugWorkspaceFolder: vscode.WorkspaceFolder;
+
 export type TestAtCursorCmd = 'debug' | 'test' | 'benchmark';
 
 /**
@@ -203,6 +208,8 @@
 		args,
 		buildFlags: buildFlags.join(' ')
 	};
+	lastDebugConfig = debugConfig;
+	lastDebugWorkspaceFolder = workspaceFolder;
 	return await vscode.debug.startDebugging(workspaceFolder, debugConfig);
 }
 
@@ -327,3 +334,14 @@
 		console.error(err);
 	});
 }
+
+/**
+ * Runs the previously executed test.
+ */
+export function debugPrevious() {
+	if (!lastDebugConfig) {
+		vscode.window.showInformationMessage('No test has been recently debugged.');
+		return;
+	}
+	return vscode.debug.startDebugging(lastDebugWorkspaceFolder, lastDebugConfig);
+}
diff --git a/src/goTools.ts b/src/goTools.ts
index c300eeb..5c04b9c 100644
--- a/src/goTools.ts
+++ b/src/goTools.ts
@@ -450,8 +450,8 @@
 		description: 'Go debugger (Delve built for DAP experiment)',
 		defaultVersion: 'master', // Always build from the master.
 		minimumGoVersion: semver.coerce('1.14'), // last 3 versions per delve policy
-		latestVersion: semver.parse('1.6.1-0.20210419181450-2408ed87bf87'),
-		latestVersionTimestamp: moment('2021-04-19', 'YYYY-MM-DD')
+		latestVersion: semver.parse('1.6.1-0.20210504195617-c5d58f494a26'),
+		latestVersionTimestamp: moment('2021-05-04', 'YYYY-MM-DD')
 	},
 	'fillstruct': {
 		name: 'fillstruct',
diff --git a/src/util.ts b/src/util.ts
index 038ac7b..b68f378 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -92,11 +92,11 @@
 	public svString?: string;
 
 	public isDevel?: boolean;
-	private commit?: string;
+	private devVersion?: string;
 
 	constructor(public binaryPath: string, public version: string) {
 		const matchesRelease = /^go version go(\d\.\d+\S*)\s+/.exec(version);
-		const matchesDevel = /go version devel \+(.[a-zA-Z0-9]+).*/.exec(version);
+		const matchesDevel = /^go version devel (\S+)\s+/.exec(version);
 		if (matchesRelease) {
 			// note: semver.parse does not work with Go version string like go1.14.
 			const sv = semver.coerce(matchesRelease[1]);
@@ -106,7 +106,7 @@
 			}
 		} else if (matchesDevel) {
 			this.isDevel = true;
-			this.commit = matchesDevel[1];
+			this.devVersion = matchesDevel[1];
 		}
 	}
 
@@ -122,7 +122,7 @@
 			return this.sv.format();
 		}
 		if (this.isDevel) {
-			return `devel +${this.commit}`;
+			return `devel ${this.devVersion}`;
 		}
 		return 'unknown';
 	}
@@ -509,15 +509,17 @@
 	const alternateTools: { [key: string]: string } = cfg.get('alternateTools');
 	const alternateToolPath: string = alternateTools[tool];
 
+	const gorootInSetting = resolvePath(cfg.get('goroot'));
+
 	let selectedGoPath: string | undefined;
-	if (tool === 'go') {
+	if (tool === 'go' && !gorootInSetting) {
 		selectedGoPath = getFromWorkspaceState('selectedGo')?.binpath;
 	}
 
 	return getBinPathWithPreferredGopathGorootWithExplanation(
 		tool,
 		tool === 'go' ? [] : [getToolsGopath(), getCurrentGoPath()],
-		tool === 'go' && cfg.get('goroot') ? resolvePath(cfg.get('goroot')) : undefined,
+		tool === 'go' ? gorootInSetting : undefined,
 		selectedGoPath ?? resolvePath(alternateToolPath),
 		useCache
 	);
diff --git a/src/utils/pathUtils.ts b/src/utils/pathUtils.ts
index bbe35d9..7f68ce5 100644
--- a/src/utils/pathUtils.ts
+++ b/src/utils/pathUtils.ts
@@ -163,7 +163,7 @@
 	}
 }
 
-export async function pathExists(p: string): Promise<boolean> {
+export async function dirExists(p: string): Promise<boolean> {
 	try {
 		const stat = promisify(fs.stat);
 		return (await stat(p)).isDirectory();
diff --git a/src/welcome.ts b/src/welcome.ts
index c03aa5b..cd26378 100644
--- a/src/welcome.ts
+++ b/src/welcome.ts
@@ -8,8 +8,8 @@
 // https://github.com/microsoft/vscode-extension-samples/tree/master/webview-sample
 
 import vscode = require('vscode');
+import path = require('path');
 import { extensionId } from './const';
-
 export class WelcomePanel {
 	public static currentPanel: WelcomePanel | undefined;
 
@@ -34,10 +34,10 @@
 				enableScripts: true,
 
 				// And restrict the webview to only loading content from our extension's directory.
-				localResourceRoots: [vscode.Uri.joinPath(extensionUri)]
+				localResourceRoots: [joinPath(extensionUri)]
 			}
 		);
-		panel.iconPath = vscode.Uri.joinPath(extensionUri, 'media', 'go-logo-blue.png');
+		panel.iconPath = joinPath(extensionUri, 'media', 'go-logo-blue.png');
 
 		WelcomePanel.currentPanel = new WelcomePanel(panel, extensionUri);
 	}
@@ -46,15 +46,15 @@
 		WelcomePanel.currentPanel = new WelcomePanel(panel, extensionUri);
 	}
 
+	public readonly dataroot: vscode.Uri; // exported for testing.
 	private readonly panel: vscode.WebviewPanel;
 	private readonly extensionUri: vscode.Uri;
-	private readonly dataroot: vscode.Uri;
 	private disposables: vscode.Disposable[] = [];
 
 	private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
 		this.panel = panel;
 		this.extensionUri = extensionUri;
-		this.dataroot = vscode.Uri.joinPath(this.extensionUri, 'media');
+		this.dataroot = joinPath(this.extensionUri, 'media');
 
 		// Set the webview's initial html content
 		this.update();
@@ -72,7 +72,7 @@
 						vscode.window.showErrorMessage(message.text);
 						return;
 					case 'openDocument':
-						const uri = vscode.Uri.joinPath(this.extensionUri, message.document);
+						const uri = joinPath(this.extensionUri, message.document);
 						vscode.commands.executeCommand('markdown.showPreviewToSide', uri);
 						return;
 					case 'openSetting':
@@ -106,9 +106,9 @@
 
 	private getHtmlForWebview(webview: vscode.Webview) {
 		// Local path to css styles and images
-		const scriptPathOnDisk = vscode.Uri.joinPath(this.dataroot, 'welcome.js');
-		const stylePath = vscode.Uri.joinPath(this.dataroot, 'welcome.css');
-		const gopherPath = vscode.Uri.joinPath(this.dataroot, 'go-logo-blue.png');
+		const scriptPathOnDisk = joinPath(this.dataroot, 'welcome.js');
+		const stylePath = joinPath(this.dataroot, 'welcome.css');
+		const gopherPath = joinPath(this.dataroot, 'go-logo-blue.png');
 		const goExtension = vscode.extensions.getExtension(extensionId)!;
 		const goExtensionVersion = goExtension.packageJSON.version;
 
@@ -194,3 +194,13 @@
 	}
 	return text;
 }
+
+function joinPath(uri: vscode.Uri, ...pathFragment: string[]): vscode.Uri {
+	// Reimplementation of
+	// https://github.com/microsoft/vscode/blob/b251bd952b84a3bdf68dad0141c37137dac55d64/src/vs/base/common/uri.ts#L346-L357
+	// with Node.JS path. This is a temporary workaround for https://github.com/eclipse-theia/theia/issues/8752.
+	if (!uri.path) {
+		throw new Error('[UriError]: cannot call joinPaths on URI without path');
+	}
+	return uri.with({ path: vscode.Uri.file(path.join(uri.fsPath, ...pathFragment)).path });
+}
diff --git a/test/gopls/survey.test.ts b/test/gopls/survey.test.ts
index b5b8424..3a32954 100644
--- a/test/gopls/survey.test.ts
+++ b/test/gopls/survey.test.ts
@@ -7,11 +7,12 @@
 import sinon = require('sinon');
 import vscode = require('vscode');
 import goLanguageServer = require('../../src/goLanguageServer');
+import goSurvey = require('../../src/goSurvey');
 
 suite('gopls survey tests', () => {
 	test('prompt for survey', () => {
 		// global state -> offer survey
-		const testCases: [goLanguageServer.SurveyConfig, boolean][] = [
+		const testCases: [goSurvey.SurveyConfig, boolean][] = [
 			// User who is activating the extension for the first time.
 			[{}, true],
 			// User who has already taken the survey.
@@ -70,7 +71,7 @@
 			sinon.replace(Math, 'random', () => 0);
 
 			const now = new Date('2020-04-29');
-			const gotPrompt = goLanguageServer.shouldPromptForGoplsSurvey(now, testConfig);
+			const gotPrompt = goSurvey.shouldPromptForSurvey(now, testConfig);
 			if (wantPrompt) {
 				assert.ok(gotPrompt, `prompt determination failed for ${i}`);
 			} else {
diff --git a/test/integration/goDebug.test.ts b/test/integration/goDebug.test.ts
index 382d865..73b9b5f 100644
--- a/test/integration/goDebug.test.ts
+++ b/test/integration/goDebug.test.ts
@@ -28,6 +28,11 @@
 import { killProcessTree } from '../../src/utils/processUtils';
 import getPort = require('get-port');
 import util = require('util');
+import { parseProgramArgSync } from '../../src/goDebugFactory';
+import { TimestampedLogger } from '../../src/goLogging';
+
+// For debugging test and streaming the trace instead of buffering, set this.
+const PRINT_TO_CONSOLE = false;
 
 suite('Path Manipulation Tests', () => {
 	test('escapeGoModPath works', () => {
@@ -330,8 +335,7 @@
 		await dc.start();
 	});
 
-	teardown(async () => {
-		await dc.stop();
+	teardown(() => {
 		if (dlvDapAdapter) {
 			const d = dlvDapAdapter;
 			dlvDapAdapter = null;
@@ -339,7 +343,9 @@
 				console.log(`${ctx.currentTest?.title} FAILED: DAP Trace`);
 				d.printLog();
 			}
-			await d.dispose();
+			d.dispose();
+		} else {
+			dc?.stop();
 		}
 		sinon.restore();
 	});
@@ -531,7 +537,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -544,7 +550,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				stopOnEntry: true
 			};
@@ -569,7 +575,7 @@
 				name: 'Launch file',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 
@@ -616,7 +622,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				dlvFlags: ['--invalid']
 			};
@@ -641,7 +647,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -665,7 +671,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -685,7 +691,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				dlvFlags: ['--listen=127.0.0.1:80']
 			};
@@ -706,7 +712,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				cwd: WD
 			};
@@ -726,7 +732,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -745,7 +751,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				cwd: WD
 			};
@@ -765,7 +771,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -775,6 +781,28 @@
 			await assertLocalVariableValue('strdat', '"Goodbye, World."');
 		});
 
+		async function waitForHelloGoodbyeOutput(dc: DebugClient): Promise<DebugProtocol.Event> {
+			return await new Promise<DebugProtocol.Event>((resolve, reject) => {
+				const listen = () => {
+					dc.waitForEvent('output', 5_000)
+						.then((event) => {
+							// Run listen again to make sure we can get the next events.
+							listen();
+							if (event.body.output === 'Hello, World!\n' || event.body.output === 'Goodbye, World.\n') {
+								// Resolve when we have found the event that we want.
+								resolve(event);
+								return;
+							}
+						})
+						.catch((reason) => reject(reason));
+				};
+				// Start listening for an output event. Especially because
+				// logging is enabled in dlv-dap, there are many output events, and it is
+				// possible to miss them if we are not prepared to handle them.
+				listen();
+			});
+		}
+
 		test('should run program with cwd set (noDebug)', async () => {
 			const WD = path.join(DATA_ROOT, 'cwdTest');
 			const PROGRAM = path.join(WD, 'cwdTest');
@@ -783,18 +811,15 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				cwd: WD,
 				noDebug: true
 			};
 			const debugConfig = await initializeDebugConfig(config);
 			dc.launch(debugConfig);
-			let found = false;
-			while (!found) {
-				const event = await dc.waitForEvent('output');
-				found = event.body.output === 'Hello, World!\n';
-			}
+			const event = await waitForHelloGoodbyeOutput(dc);
+			assert.strictEqual(event.body.output, 'Hello, World!\n');
 		});
 
 		test('should run program without cwd set (noDebug)', async () => {
@@ -805,17 +830,14 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				noDebug: true
 			};
 			const debugConfig = await initializeDebugConfig(config);
 			dc.launch(debugConfig);
-			let found = false;
-			while (!found) {
-				const event = await dc.waitForEvent('output');
-				found = event.body.output === 'Goodbye, World.\n';
-			}
+			const event = await waitForHelloGoodbyeOutput(dc);
+			assert.strictEqual(event.body.output, 'Goodbye, World.\n');
 		});
 
 		test('should run file program with cwd set (noDebug)', async () => {
@@ -826,20 +848,16 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				cwd: WD,
 				noDebug: true
 			};
 			const debugConfig = await initializeDebugConfig(config);
 			dc.launch(debugConfig);
-			let found = false;
-			while (!found) {
-				const event = await dc.waitForEvent('output');
-				found = event.body.output === 'Hello, World!\n';
-			}
+			const event = await waitForHelloGoodbyeOutput(dc);
+			assert.strictEqual(event.body.output, 'Hello, World!\n');
 		});
-
 		test('should run file program without cwd set (noDebug)', async () => {
 			const WD = path.join(DATA_ROOT, 'cwdTest');
 			const PROGRAM = path.join(WD, 'cwdTest', 'main.go');
@@ -848,17 +866,14 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				noDebug: true
 			};
 			const debugConfig = await initializeDebugConfig(config);
 			dc.launch(debugConfig);
-			let found = false;
-			while (!found) {
-				const event = await dc.waitForEvent('output');
-				found = event.body.output === 'Goodbye, World.\n';
-			}
+			const event = await waitForHelloGoodbyeOutput(dc);
+			assert.strictEqual(event.body.output, 'Goodbye, World.\n');
 		});
 	});
 
@@ -936,7 +951,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1031,7 +1046,7 @@
 				name: 'Launch file',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1061,7 +1076,7 @@
 				name: 'Launch file',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1125,7 +1140,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1161,7 +1176,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1201,7 +1216,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1256,7 +1271,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM_WITH_EXCEPTION
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1290,7 +1305,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM_WITH_EXCEPTION
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1317,7 +1332,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM_WITH_EXCEPTION
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1384,7 +1399,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				stopOnEntry: false
 			};
@@ -1405,7 +1420,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				stopOnEntry: false
 			};
@@ -1430,7 +1445,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				stopOnEntry: true
 			};
@@ -1457,7 +1472,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				stopOnEntry: false
 			};
@@ -1481,7 +1496,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1507,7 +1522,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
@@ -1528,7 +1543,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				stopOnEntry: true
 			};
@@ -1550,7 +1565,7 @@
 				name: 'Launch',
 				type: 'go',
 				request: 'launch',
-				mode: 'auto',
+				mode: 'debug',
 				program: PROGRAM,
 				stopOnEntry: true
 			};
@@ -1563,13 +1578,59 @@
 
 			await Promise.all([dc.disconnectRequest({ restart: false }), dc.waitForEvent('terminated')]);
 		});
+
+		test('should cleanup when stopped', async function () {
+			if (!isDlvDap || !dlvDapSkipsEnabled) {
+				this.skip();
+			}
+			const PROGRAM = path.join(DATA_ROOT, 'loop');
+			const OUTPUT = path.join(PROGRAM, '_loop_output');
+
+			const config = {
+				name: 'Launch',
+				type: 'go',
+				request: 'launch',
+				mode: 'debug',
+				program: PROGRAM,
+				stopOnEntry: false,
+				output: OUTPUT
+			};
+			const debugConfig = await initializeDebugConfig(config);
+
+			await Promise.all([dc.configurationSequence(), dc.launch(debugConfig)]);
+
+			try {
+				const fsstat = util.promisify(fs.stat);
+				await fsstat(OUTPUT);
+			} catch (e) {
+				assert.fail(`debug output ${OUTPUT} wasn't built: ${e}`);
+			}
+
+			// It's possible dlv-dap doesn't respond. So, don't wait.
+			dc.disconnectRequest({ restart: false });
+			await sleep(10);
+			dlvDapAdapter.dispose(1);
+			dc = undefined;
+			await sleep(100); // allow dlv to respond and finish cleanup.
+			let stat: fs.Stats = null;
+			try {
+				const fsstat = util.promisify(fs.stat);
+				stat = await fsstat(OUTPUT);
+				fs.unlinkSync(OUTPUT);
+			} catch (e) {
+				console.log(`output was cleaned ${OUTPUT} ${e}`);
+			}
+			assert.strictEqual(stat, null, `debug output ${OUTPUT} wasn't cleaned up. ${JSON.stringify(stat)}`);
+			console.log('finished');
+		});
 	});
 
 	suite('switch goroutine', () => {
 		async function runSwitchGoroutineTest(stepFunction: string) {
 			const PROGRAM = path.join(DATA_ROOT, 'goroutineTest');
 			const FILE = path.join(PROGRAM, 'main.go');
-			const BREAKPOINT_LINE = 14;
+			const BREAKPOINT_LINE_MAIN_RUN1 = 6;
+			const BREAKPOINT_LINE_MAIN_RUN2 = 14;
 
 			const config = {
 				name: 'Launch',
@@ -1579,18 +1640,33 @@
 				program: PROGRAM
 			};
 			const debugConfig = await initializeDebugConfig(config);
-			await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE));
-			// Clear breakpoints to make sure they do not interrupt the stepping.
+			// Set a breakpoint in run 1 and get the goroutine id.
+			await dc.hitBreakpoint(debugConfig, getBreakpointLocation(FILE, BREAKPOINT_LINE_MAIN_RUN1));
+			const threadsResponse1 = await dc.threadsRequest();
+			assert.ok(threadsResponse1.success);
+			const run1Goroutine = threadsResponse1.body.threads.find((val) => val.name.indexOf('main.run1') >= 0);
+
+			// Set a breakpoint in run 2 and get the goroutine id.
+			// By setting breakpoints in both goroutine, we can make sure that both goroutines
+			// are running before continuing.
+			const bp2 = getBreakpointLocation(FILE, BREAKPOINT_LINE_MAIN_RUN2);
 			const breakpointsResult = await dc.setBreakpointsRequest({
+				source: { path: bp2.path },
+				breakpoints: [{ line: bp2.line }]
+			});
+			assert.ok(breakpointsResult.success);
+			const threadsResponse2 = await dc.threadsRequest();
+			assert.ok(threadsResponse2.success);
+			const run2Goroutine = threadsResponse2.body.threads.find((val) => val.name.indexOf('main.run2') >= 0);
+
+			await Promise.all([dc.continueRequest({ threadId: 1 }), dc.assertStoppedLocation('breakpoint', bp2)]);
+
+			// Clear breakpoints to make sure they do not interrupt the stepping.
+			const clearBreakpointsResult = await dc.setBreakpointsRequest({
 				source: { path: FILE },
 				breakpoints: []
 			});
-			assert.ok(breakpointsResult.success);
-
-			const threadsResponse = await dc.threadsRequest();
-			assert.ok(threadsResponse.success);
-			const run1Goroutine = threadsResponse.body.threads.find((val) => val.name.indexOf('main.run1') >= 0);
-			const run2Goroutine = threadsResponse.body.threads.find((val) => val.name.indexOf('main.run2') >= 0);
+			assert.ok(clearBreakpointsResult.success);
 
 			// runStepFunction runs the necessary step function and resolves if it succeeded.
 			async function runStepFunction(
@@ -1692,6 +1768,73 @@
 		});
 	});
 
+	suite('logDest attribute tests', () => {
+		const PROGRAM = path.join(DATA_ROOT, 'baseTest');
+
+		let tmpDir: string;
+		suiteSetup(() => {
+			tmpDir = fs.mkdtempSync(path.join(tmpdir(), 'logDestTest'));
+		});
+		suiteTeardown(() => {
+			rmdirRecursive(tmpDir);
+		});
+
+		test('logs are written to logDest file', async function () {
+			if (!isDlvDap || process.platform === 'win32') {
+				this.skip();
+			}
+			const DELVE_LOG = path.join(tmpDir, 'delve.log');
+			const config = {
+				name: 'Launch',
+				type: 'go',
+				request: 'launch',
+				mode: 'debug',
+				program: PROGRAM,
+				logDest: DELVE_LOG
+			};
+
+			const debugConfig = await initializeDebugConfig(config);
+			await Promise.all([dc.configurationSequence(), dc.launch(debugConfig), dc.waitForEvent('terminated')]);
+			await dc.stop();
+			dc = undefined;
+			const dapLog = fs.readFileSync(DELVE_LOG)?.toString();
+			assert(
+				dapLog.includes('DAP server listening at') &&
+					dapLog.includes('"command":"initialize"') &&
+					dapLog.includes('"event":"terminated"'),
+				dapLog
+			);
+		});
+
+		async function testWithInvalidLogDest(logDest: any, wantedErrorMessage: string) {
+			const config = {
+				name: 'Launch',
+				type: 'go',
+				request: 'launch',
+				mode: 'debug',
+				program: PROGRAM,
+				logDest
+			};
+
+			try {
+				await initializeDebugConfig(config);
+			} catch (error) {
+				assert(error?.message.includes(wantedErrorMessage), `unexpected error: ${error}`);
+				return;
+			}
+			assert.fail('dlv dap started normally, wanted the invalid logDest to cause failure');
+		}
+		test('relative path as logDest triggers an error', async function () {
+			if (!isDlvDap || process.platform === 'win32') this.skip();
+			await testWithInvalidLogDest('delve.log', 'relative path');
+		});
+
+		test('number as logDest triggers an error', async function () {
+			if (!isDlvDap || process.platform === 'win32') this.skip();
+			await testWithInvalidLogDest(3, 'file descriptor');
+		});
+	});
+
 	suite('substitute path', () => {
 		// TODO(suzmue): add unit tests for substitutePath.
 		let tmpDir: string;
@@ -1850,17 +1993,30 @@
 		});
 	});
 
+	let testNumber = 0;
 	async function initializeDebugConfig(config: DebugConfiguration) {
 		if (isDlvDap) {
 			config['debugAdapter'] = 'dlv-dap';
 			// Log the output for easier test debugging.
 			config['logOutput'] = 'dap';
 			config['showLog'] = true;
+			config['trace'] = 'verbose';
 		}
 
+		// Give each test a distinct debug binary. If a previous test
+		// and a new test use the same binary location, it is possible
+		// that the second test could build the binary, and then the
+		// first test could delete that binary during cleanup before the
+		// second test has a chance to run it.
+		if (!config['output'] && config['mode'] !== 'remote') {
+			const dir = parseProgramArgSync(config).dirname;
+			config['output'] = path.join(dir, `__debug_bin_${testNumber}`);
+		}
+		testNumber++;
+
 		const debugConfig = await debugConfigProvider.resolveDebugConfiguration(undefined, config);
 		if (isDlvDap) {
-			dlvDapAdapter = new DelveDAPDebugAdapterOnSocket(debugConfig);
+			dlvDapAdapter = await DelveDAPDebugAdapterOnSocket.create(debugConfig);
 			const port = await dlvDapAdapter.serve();
 			await dc.start(port); // This will connect to the adapter's port.
 		}
@@ -1883,8 +2039,14 @@
 // the thin adapter for Delve DAP and the debug test support's
 // DebugClient to communicate with the adapter over a network socket.
 class DelveDAPDebugAdapterOnSocket extends proxy.DelveDAPOutputAdapter {
-	constructor(config: DebugConfiguration) {
-		super(config, false);
+	static async create(config: DebugConfiguration) {
+		const d = new DelveDAPDebugAdapterOnSocket(config);
+		await d.startAndConnectToServer();
+		return d;
+	}
+
+	private constructor(config: DebugConfiguration) {
+		super(config, new TimestampedLogger('error', undefined, PRINT_TO_CONSOLE));
 	}
 
 	private static TWO_CRLF = '\r\n\r\n';
@@ -1923,14 +2085,15 @@
 	}
 
 	private _disposed = false;
-	public async dispose() {
+	public async dispose(timeoutMS?: number) {
 		if (this._disposed) {
 			return;
 		}
 		this._disposed = true;
-		this.log('adapter disposed');
+		this.log('adapter disposing');
 		await this._server.close();
-		await super.dispose();
+		await super.dispose(timeoutMS);
+		this.log('adapter disposed');
 	}
 
 	// Code from
@@ -1991,13 +2154,19 @@
 			break;
 		}
 	}
-
 	/* --- accumulate log messages so we can output when the test fails --- */
 	private _log = [] as string[];
 	private log(msg: string) {
 		this._log.push(msg);
+		if (PRINT_TO_CONSOLE) {
+			console.log(msg);
+		}
 	}
 	public printLog() {
 		this._log.forEach((msg) => console.log(msg));
 	}
 }
+
+function sleep(ms: number) {
+	return new Promise((resolve) => setTimeout(resolve, ms));
+}
diff --git a/test/integration/goDebugConfiguration.test.ts b/test/integration/goDebugConfiguration.test.ts
index ab29b5d..d2c112d 100644
--- a/test/integration/goDebugConfiguration.test.ts
+++ b/test/integration/goDebugConfiguration.test.ts
@@ -390,3 +390,67 @@
 		});
 	});
 });
+
+suite('Debug Configuration Resolve Paths', () => {
+	const debugConfigProvider = new GoDebugConfigurationProvider();
+	test('resolve ~ in cwd', () => {
+		const config = {
+			name: 'Launch',
+			type: 'go',
+			request: 'launch',
+			mode: 'auto',
+			program: '${fileDirname}',
+			cwd: '~/main.go'
+		};
+
+		debugConfigProvider.resolveDebugConfiguration(undefined, config);
+		assert.notStrictEqual(config.cwd, '~/main.go');
+	});
+
+	test('do not resolve workspaceFolder or fileDirname', () => {
+		const config = {
+			name: 'Launch',
+			type: 'go',
+			request: 'launch',
+			mode: 'auto',
+			program: '${fileDirname}',
+			cwd: '${workspaceFolder}'
+		};
+
+		debugConfigProvider.resolveDebugConfiguration(undefined, config);
+
+		assert.strictEqual(config.cwd, '${workspaceFolder}');
+		assert.strictEqual(config.program, '${fileDirname}');
+	});
+});
+
+suite('Debug Configuration Auto Mode', () => {
+	const debugConfigProvider = new GoDebugConfigurationProvider();
+	test('resolve auto to debug with non-test file', () => {
+		const config = {
+			name: 'Launch',
+			type: 'go',
+			request: 'launch',
+			mode: 'auto',
+			program: '/path/to/main.go'
+		};
+
+		debugConfigProvider.resolveDebugConfiguration(undefined, config);
+		assert.strictEqual(config.mode, 'debug');
+		assert.strictEqual(config.program, '/path/to/main.go');
+	});
+
+	test('resolve auto to debug with test file', () => {
+		const config = {
+			name: 'Launch',
+			type: 'go',
+			request: 'launch',
+			mode: 'auto',
+			program: '/path/to/main_test.go'
+		};
+
+		debugConfigProvider.resolveDebugConfiguration(undefined, config);
+		assert.strictEqual(config.mode, 'test');
+		assert.strictEqual(config.program, '/path/to');
+	});
+});
diff --git a/test/integration/statusbar.test.ts b/test/integration/statusbar.test.ts
index fd22f9e..8db7df1 100644
--- a/test/integration/statusbar.test.ts
+++ b/test/integration/statusbar.test.ts
@@ -7,13 +7,11 @@
  *--------------------------------------------------------*/
 
 import * as assert from 'assert';
-import * as cp from 'child_process';
 import * as fs from 'fs-extra';
 import { describe, it } from 'mocha';
 import * as os from 'os';
 import * as path from 'path';
 import * as sinon from 'sinon';
-import * as util from 'util';
 import * as vscode from 'vscode';
 import {
 	formatGoVersion,
@@ -25,7 +23,6 @@
 import { updateGoVarsFromConfig } from '../../src/goInstallTools';
 import { disposeGoStatusBar } from '../../src/goStatus';
 import { getWorkspaceState, setWorkspaceState } from '../../src/stateUtils';
-import { getCurrentGoRoot } from '../../src/utils/pathUtils';
 import { MockMemento } from '../mocks/MockMemento';
 
 import ourutil = require('../../src/util');
@@ -55,15 +52,15 @@
 	});
 });
 
-describe('#setSelectedGo()', async function () {
+describe('#setSelectedGo()', function () {
 	this.timeout(40000);
 	let sandbox: sinon.SinonSandbox | undefined;
 	let goOption: GoEnvironmentOption;
 	let defaultMemento: vscode.Memento;
-	const version = await ourutil.getGoVersion();
-	const defaultGoOption = new GoEnvironmentOption(version.binaryPath, formatGoVersion(version));
 
 	this.beforeAll(async () => {
+		const version = await ourutil.getGoVersion();
+		const defaultGoOption = new GoEnvironmentOption(version.binaryPath, formatGoVersion(version));
 		defaultMemento = getWorkspaceState();
 		setWorkspaceState(new MockMemento());
 		await setSelectedGo(defaultGoOption);
@@ -89,7 +86,9 @@
 		assert.equal(getSelectedGo()?.binpath, 'workspacetestpath');
 	});
 
-	it('should download an uninstalled version of Go', async () => {
+	it.skip('should download an uninstalled version of Go', async () => {
+		// TODO(https://github.com/golang/vscode-go/issues/1454): temporarily disabled
+		// to unblock nightly release during investigation.
 		if (!process.env['VSCODEGO_BEFORE_RELEASE_TESTS']) {
 			return;
 		}
@@ -110,109 +109,3 @@
 		process.env = envCache;
 	});
 });
-
-describe('#updateGoVarsFromConfig()', async function () {
-	this.timeout(10000);
-
-	let defaultMemento: vscode.Memento;
-	let tmpRoot: string | undefined;
-	let tmpRootBin: string | undefined;
-	let sandbox: sinon.SinonSandbox | undefined;
-	const version = await ourutil.getGoVersion();
-	const defaultGoOption = new GoEnvironmentOption(version.binaryPath, formatGoVersion(version));
-
-	this.beforeAll(async () => {
-		defaultMemento = getWorkspaceState();
-		setWorkspaceState(new MockMemento());
-		await setSelectedGo(defaultGoOption);
-
-		tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'rootchangetest'));
-		tmpRootBin = path.join(tmpRoot, 'bin');
-
-		// build a fake go binary and place it in tmpRootBin.
-		fs.mkdirSync(tmpRootBin);
-
-		const fixtureSourcePath = path.join(__dirname, '..', '..', '..', 'test', 'testdata', 'testhelpers');
-		const execFile = util.promisify(cp.execFile);
-		const goRuntimePath = ourutil.getBinPath('go');
-		const { stderr } = await execFile(goRuntimePath, [
-			'build',
-			'-o',
-			path.join(tmpRootBin, 'go'),
-			path.join(fixtureSourcePath, 'fakego.go')
-		]);
-		if (stderr) {
-			assert.fail(`failed to build the fake go binary required for testing: ${stderr}`);
-		}
-	});
-
-	this.afterAll(async () => {
-		setWorkspaceState(defaultMemento);
-		ourutil.rmdirRecursive(tmpRoot);
-		await updateGoVarsFromConfig();
-	});
-
-	this.beforeEach(() => {
-		sandbox = sinon.createSandbox();
-	});
-
-	this.afterEach(() => {
-		sandbox.restore();
-	});
-
-	function pathEnvVar(): string[] {
-		let paths = [] as string[];
-		if (process.env.hasOwnProperty('PATH')) {
-			paths = process.env['PATH'].split(path.delimiter);
-		} else if (process.platform === 'win32' && process.env.hasOwnProperty('Path')) {
-			paths = process.env['Path'].split(path.delimiter);
-		}
-		return paths;
-	}
-
-	it('should have a sensible goroot with the default setting', async () => {
-		await updateGoVarsFromConfig();
-
-		const b = getGoEnvironmentStatusbarItem();
-		assert.ok(b.text.startsWith('Go'), `go env statusbar item = ${b.text}, want "Go..."`);
-		assert.equal(
-			pathEnvVar()[0],
-			[path.join(getCurrentGoRoot(), 'bin')],
-			'the first element in PATH must match the current GOROOT/bin'
-		);
-	});
-
-	it('should recognize the adjusted goroot using go.goroot', async () => {
-		// adjust the fake go binary's behavior.
-		process.env['FAKEGOROOT'] = tmpRoot;
-		process.env['FAKEGOVERSION'] = 'go version go2.0.0 darwin/amd64';
-
-		await updateGoVarsFromConfig();
-
-		assert.equal((await ourutil.getGoVersion()).format(), '2.0.0');
-		assert.equal(getGoEnvironmentStatusbarItem().text, 'Go 2.0.0');
-		assert.equal(
-			pathEnvVar()[0],
-			[path.join(getCurrentGoRoot(), 'bin')],
-			'the first element in PATH must match the current GOROOT/bin'
-		);
-	});
-
-	it('should recognize the adjusted goroot using go.alternateTools', async () => {
-		// "go.alternateTools" : {"go": "go3"}
-		fs.copyFileSync(path.join(tmpRootBin, 'go'), path.join(tmpRootBin, 'go3'));
-
-		process.env['FAKEGOROOT'] = tmpRoot;
-		process.env['FAKEGOVERSION'] = 'go version go3.0.0 darwin/amd64';
-
-		await updateGoVarsFromConfig();
-
-		assert.equal((await ourutil.getGoVersion()).format(), '3.0.0');
-		assert.equal(getGoEnvironmentStatusbarItem().text, 'Go 3.0.0');
-		assert.equal(
-			pathEnvVar()[0],
-			[path.join(getCurrentGoRoot(), 'bin')],
-			'the first element in PATH must match the current GOROOT/bin'
-		);
-	});
-});
diff --git a/test/integration/utils.test.ts b/test/integration/utils.test.ts
index 6620157..b9102b6 100644
--- a/test/integration/utils.test.ts
+++ b/test/integration/utils.test.ts
@@ -35,6 +35,12 @@
 				'devel +a295d59d',
 				true
 			],
+			[
+				'go version devel go1.17-756fd56bbf Thu Apr 29 01:15:34 2021 +0000 darwin/amd64',
+				'devel go1.17-756fd56bbf',
+				'devel go1.17-756fd56bbf',
+				true
+			],
 			['go version go1.14 darwin/amd64', '1.14.0', '1.14', true],
 			['go version go1.14.1 linux/amd64', '1.14.1', '1.14.1', true],
 			['go version go1.15rc1 darwin/amd64', '1.15.0', '1.15rc1', true],
diff --git a/test/integration/welcome.test.ts b/test/integration/welcome.test.ts
index c85ede7..48aff48 100644
--- a/test/integration/welcome.test.ts
+++ b/test/integration/welcome.test.ts
@@ -3,8 +3,11 @@
  * Licensed under the MIT License. See LICENSE in the project root for license information.
  *--------------------------------------------------------*/
 
+import vscode = require('vscode');
 import * as assert from 'assert';
 import { shouldShowGoWelcomePage } from '../../src/goMain';
+import { extensionId } from '../../src/const';
+import { WelcomePanel } from '../../src/welcome';
 
 suite('WelcomePanel Tests', () => {
 	// 0:showVersions, 1:newVersion, 2:oldVersion, 3:expected
@@ -55,3 +58,13 @@
 		});
 	});
 });
+
+suite('joinPath Tests', () => {
+	test('WelcomePanel dataroot is set as expected', () => {
+		const uri = vscode.extensions.getExtension(extensionId).extensionUri;
+		WelcomePanel.createOrShow(uri);
+		const got = WelcomePanel.currentPanel.dataroot;
+		const want = vscode.Uri.joinPath(uri, 'media');
+		assert.strictEqual(got.toString(), want.toString());
+	});
+});
diff --git a/test/runTest.ts b/test/runTest.ts
index b44b2f7..de9eaa8 100644
--- a/test/runTest.ts
+++ b/test/runTest.ts
@@ -27,7 +27,7 @@
 			extensionTestsPath,
 			launchArgs: [
 				'--disable-extensions',
-				'--user-data-dir=${workspaceFolder}/.user-data-dir-test',
+				`--user-data-dir=${extensionDevelopmentPath}/.user-data-dir-test`,
 				// https://github.com/microsoft/vscode/issues/115794#issuecomment-774283222
 				'--force-disable-user-env'
 			]
@@ -48,7 +48,7 @@
 			extensionTestsPath: path.resolve(__dirname, './gopls/index'),
 			launchArgs: [
 				'--disable-extensions', // disable all other extensions
-				'--user-data-dir=${workspaceFolder}/.user-data-dir-test',
+				`--user-data-dir=${extensionDevelopmentPath}/.user-data-dir-test`,
 				// https://github.com/microsoft/vscode/issues/115794#issuecomment-774283222
 				'--force-disable-user-env'
 			]
diff --git a/test/testdata/loop/loop.go b/test/testdata/loop/loop.go
index 9740987..394e063 100644
--- a/test/testdata/loop/loop.go
+++ b/test/testdata/loop/loop.go
@@ -1,7 +1,7 @@
 package main
-
+import "time"
 func main() {
 	for {
-		print("Hello")
+		time.Sleep(10*time.Millisecond)
 	}
 }
diff --git a/test/testdata/testhelpers/fakego.go b/test/testdata/testhelpers/fakego.go
deleted file mode 100644
index 8116833..0000000
--- a/test/testdata/testhelpers/fakego.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2020 The Go Authors. All rights reserved.
-// Licensed under the MIT License.
-// See LICENSE in the project root for license information.
-
-// This is a helper used to fake a go binary.
-// It currently fakes `go env` and `go version` commands.
-// For `go env`, it returns FAKEGOROOT for 'GOROOT', and an empty string for others.
-// For `go version`, it returns FAKEGOVERSION or a default dev version string.
-package main
-
-import (
-	"fmt"
-	"os"
-)
-
-func main() {
-	args := os.Args
-
-	if len(args) <= 1 {
-		return
-	}
-	switch args[1] {
-	case "env":
-		fakeEnv(args[2:])
-	case "version":
-		fakeVersion()
-	default:
-		fmt.Fprintf(os.Stderr, "not implemented")
-		os.Exit(1)
-	}
-	os.Exit(0)
-}
-
-func fakeEnv(args []string) {
-	for _, a := range args {
-		fmt.Println(os.Getenv("FAKE" + a))
-	}
-}
-
-func fakeVersion() {
-	ver := os.Getenv("FAKEGOVERSION")
-	if ver != "" {
-		fmt.Println(ver)
-		return
-	}
-	fmt.Println("go version devel +a07e2819 Thu Jun 18 20:58:26 2020 +0000 darwin/amd64")
-}
diff --git a/test/unit/logger.test.ts b/test/unit/logger.test.ts
new file mode 100644
index 0000000..1cbc970
--- /dev/null
+++ b/test/unit/logger.test.ts
@@ -0,0 +1,41 @@
+/*---------------------------------------------------------
+ * Copyright 2021 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+import * as assert from 'assert';
+import sinon = require('sinon');
+import { Logger } from '../../src/goLogging';
+
+suite('Logger Tests', () => {
+	let sandbox: sinon.SinonSandbox;
+
+	setup(() => {
+		sandbox = sinon.createSandbox();
+	});
+	teardown(() => {
+		sandbox.restore();
+	});
+
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	function runTest(level: any, want: number) {
+		const appendLine = sandbox.fake();
+		const logger = new Logger(level, { appendLine });
+		logger.error('error');
+		logger.warn('warn');
+		logger.info('info');
+		logger.debug('debug');
+		logger.trace('trace');
+		assert.strictEqual(appendLine.callCount, want, `called ${appendLine.callCount} times, want ${want}`);
+	}
+	test('logger level = off', () => runTest('off', 0));
+	test('logger level = error', () => runTest('error', 1));
+	test('logger level = warning', () => runTest('warn', 2));
+	test('logger level = info', () => runTest('info', 3));
+	test('logger level = trace', () => runTest('trace', 4));
+	test('logger level = verbose', () => runTest('verbose', 5));
+	test('logger level = undefined', () => runTest(undefined, 1));
+	test('logger level = ""', () => runTest('', 1));
+	test('logger level = object', () => runTest({}, 1));
+	test('logger level = number', () => runTest(10, 1));
+});