[release] prepare v0.41.3 release

d0d95aba extension/CHANGELOG.md: add a note for v0.41.3
b501de3e Revert "docs: update the debugging doc about remote mode default adapter"
9330b086 extension/src/goTelemetry: do our JSON enc/dec for telemetry start time
f8173bc9 extension/src/welcome: fix 'go.showWelcome' setting interpretation
ad37a53c Revert "src/goDebugConfiguration: change remote/attach default to dlv-dap"
40990c07 extension: resolve variables in customFormatter field.
78deb717 docs: fix tools information doc link

Change-Id: Ia32f0b7babc681365a02f5fd99b25573fb7934ab
diff --git a/docs/debugging-legacy.md b/docs/debugging-legacy.md
index 734b6c5..6af77db 100644
--- a/docs/debugging-legacy.md
+++ b/docs/debugging-legacy.md
@@ -2,7 +2,7 @@
 
 The Go extension historically used a small adapter program to work with the Go debugger, [Delve].
 The extension transitioned to communicate with [Delve] directly but there are still cases you may
-need to use the legacy debug adapter. This document explains how to use the
+need to use the legacy debug adapter (e.g. remote debugging). This document explains how to use the
 ***legacy*** debug adapter.
 
 
@@ -45,6 +45,8 @@
 ```
 
 If you want to use the legacy mode for only a subset of your launch configurations, you can use [the `debugAdapter` attribute](#launchjson-attributes) to switch between `"dlv-dap"` and `"legacy"` mode.
+For [Remote Debugging](#remote-debugging) (launch configuration with `"mode": "remote"` attribute),
+the extension will use the `"legacy"` mode by default, so setting this attribute won't be necessary.
 
 Throughout this document, we assume that you opted in to use the legacy debug adapter.
 For debugging using the new debug adapter (default, `"dlv-dap"` mode), please see the documentation about [Debugging](https://github.com/golang/vscode-go/tree/master/docs/debugging-legacy.md).
diff --git a/docs/debugging.md b/docs/debugging.md
index 044519f..643e9a3 100644
--- a/docs/debugging.md
+++ b/docs/debugging.md
@@ -6,8 +6,26 @@
 [VS Code’s Debugging UI](https://code.visualstudio.com/docs/editor/debugging).
 
 These debugging features are possible by using
-[Delve](https://github.com/go-delve/delve), the Go debugger, and its
-[native debug adapter implementation](https://github.com/go-delve/delve/tree/master/service/dap).
+[Delve](https://github.com/go-delve/delve), the Go debugger.
+
+Previously, the Go extension communicated with Delve through a custom debug
+adaptor program (`legacy` mode). Since
+[`Delve`'s native debug adapter implementation](https://github.com/go-delve/delve/tree/master/service/dap)
+is available, the Go extension is transitioning to deprecate the legacy debug
+adapter in favor of direct communication with Delve via
+[DAP](https://microsoft.github.io/debug-adapter-protocol/overview).
+
+
+ 📣 **We are happy to announce that the new _`dlv-dap`_ mode of Delve
+ integration is enabled for _local_ _debugging_ by default. For
+ [_remote_ _debugging_](#remote-debugging) it is the default in
+ [Go Nightly](nightly.md) and is available with stable builds on demand with
+ `"debugAdapter": "dlv-dap"` attribute in `launch.json` or `settings.json`!**
+
+Many features and settings described in this document may be available only with
+the new `dlv-dap` mode. For troubleshooting and configuring the legacy debug
+adapter, see
+[the legacy debug adapter documentation](https://github.com/golang/vscode-go/tree/master/docs/debugging-legacy.md).
 
 ## Get started
 
@@ -64,9 +82,7 @@
 
 ### Switch to legacy debug adapter
 
-Previously, the Go extension communicated with Delve through a custom debug
-adaptor program (aka `legacy` mode). This legacy adapter is no longer maintained
-and will be removed by the end of 2024 H2.
+Note: The extension still uses the legacy debug adapter for remote debugging.
 
 If you need to use the legacy debug adapter for local debugging (`legacy` mode)
 by default, add the following in your VSCode settings.
@@ -77,6 +93,9 @@
     }
 ```
 
+When `mode` is set to `remote` you must explicitly set `debugAdapter` to
+`dlv-dap` to override the legacy adapter default.
+
 If you want to switch to `legacy` for only a subset of your launch
 configurations, you can use
 [the `debugAdapter` attribute](#launchjson-attributes) to switch between
@@ -790,8 +809,8 @@
 
 The
 [headless dlv server](https://github.com/go-delve/delve/tree/master/Documentation/api)
-can now be used with both `"debugAdapter": "dlv-dap"` (default) and
-`"debugAdapter": "legacy"` (with Delve v1.7.3 or newer) as well as Delve's
+can now be used with both `"debugAdapter": "legacy"` (default value) and
+`"debugAdapter": "dlv-dap"` (with Delve v1.7.3 or newer) as well as Delve's
 [command-line interface](https://github.com/go-delve/delve/tree/master/Documentation/cli)
 via `dlv connect`. The `--accept-multiclient` flag makes this a multi-use server
 that persists on `Disconnect` from a client and allows repeated connections from
@@ -818,6 +837,7 @@
 {
     "name": "Connect to external session",
     "type": "go",
+    "debugAdapter": "dlv-dap", // `legacy` by default
     "request": "attach",
     "mode": "remote",
     "port": 12345,
diff --git a/docs/tools.md b/docs/tools.md
index 7f998cb..10ff1ed 100644
--- a/docs/tools.md
+++ b/docs/tools.md
@@ -2,7 +2,7 @@
 
 This document describes the tools that power the VS Code Go extension.
 
-Tools will be installed by default when you install the extension. You can also manually install or update all of these tools by running the [`Go: Install/Update Tools`](commands.md#go-installupdate-tools) command. The extension uses pinned versions of command-line tools. See the pinned versions in tools information [here](https://github.com/golang/vscode-go/blob/master/src/goToolsInformation.ts). If any tools are missing, you will see an `Analysis Tools Missing` warning in the bottom-right corner of the editor, which will prompt you to install these tools.
+Tools will be installed by default when you install the extension. You can also manually install or update all of these tools by running the [`Go: Install/Update Tools`](commands.md#go-installupdate-tools) command. The extension uses pinned versions of command-line tools. See the pinned versions in tools information [here](https://github.com/golang/vscode-go/blob/master/extension/src/goToolsInformation.ts). If any tools are missing, you will see an `Analysis Tools Missing` warning in the bottom-right corner of the editor, which will prompt you to install these tools.
 
 VS Code Go will install the tools to your `$GOPATH/bin` by default. 
 
diff --git a/extension/CHANGELOG.md b/extension/CHANGELOG.md
index cd4d431..38235e8 100644
--- a/extension/CHANGELOG.md
+++ b/extension/CHANGELOG.md
@@ -1,3 +1,41 @@
+## v0.41.3 - 22 Apr, 2024
+
+This point release temporarily reverts the default remote debugging behavior to
+use the legacy debug adapter due to existing feature gaps.
+Once these gaps are addressed as outlined in
+[Issue 3096](https://github.com/golang/vscode-go/issues/3096),
+we plan to switch the default back to use Delve DAP for remote deubgging.
+
+If you want to continue using [Delve DAP for remote debugging](https://github.com/golang/vscode-go/wiki/debugging#connect-to-headless-delve-with-target-specified-at-server-start-up), use the following
+settings in your `launch.json`.
+
+```json
+{
+    "name": "Debug Remote",
+    "type": "go",
+    "request": "attach",
+    "mode": "remote",
+    "debugAdapter": "dlv-dap", // Use Delve DAP
+    ...
+}
+```
+
+See the full
+[commit history](https://github.com/golang/vscode-go/compare/v0.41.2...v0.41.3)
+for detailed changes.
+
+### Fixes
+- Fixed to substitute variables used in `go.alternateTools` setting's
+  `customFormatter` entry ([Issue 2582](https://github.com/golang/vscode-go/issues/2582)).
+- Corrected handling of `go.showWelcome` setting
+  ([Issue 3319](https://github.com/golang/vscode-go/issues/3319)).
+- Addressed Go telemetry prompt issue in the latest Visual Studio Code
+  ([Issue 3312](https://github.com/golang/vscode-go/issues/3312)).
+
+### Thanks
+
+Thanks for your contributions, @uniquefine, @monitor1379, @suzmue, @hyangah!
+
 ## v0.41.2 - 14 Mar, 2024
 
 This release is a point release to increase the prompt rate of Go telemetry
diff --git a/extension/src/goDebugConfiguration.ts b/extension/src/goDebugConfiguration.ts
index caebcea..aa57a06 100644
--- a/extension/src/goDebugConfiguration.ts
+++ b/extension/src/goDebugConfiguration.ts
@@ -183,35 +183,24 @@
 			}
 		}
 		// If neither launch.json nor settings.json gave us the debugAdapter value, we go with the default
-		// from package.json (dlv-dap).
+		// from package.json (dlv-dap) unless this is remote attach with a stable release.
 		if (!debugConfiguration['debugAdapter']) {
 			debugConfiguration['debugAdapter'] = defaultConfig.debugAdapter.default;
-			if (
-				debugConfiguration.request === 'attach' &&
-				debugConfiguration['mode'] === 'remote' &&
-				!extensionInfo.isPreview
-			) {
-				this.showWarning(
-					'ignoreDefaultDebugAdapterChangeWarning',
-					"We are using the 'dlv-dap' integration for remote debugging by default. Please comment on [issue 3096](https://github.com/golang/vscode-go/issues/3096) if this impacts your workflows."
-				);
+			if (debugConfiguration['mode'] === 'remote' && !extensionInfo.isPreview) {
+				debugConfiguration['debugAdapter'] = 'legacy';
 			}
 		}
-		if (debugConfiguration['debugAdapter'] === 'legacy') {
-			this.showWarning(
-				'ignoreLegacyDADeprecationWarning',
-				'Legacy debug adapter is deprecated. Please comment on [issue 3096](https://github.com/golang/vscode-go/issues/3096) if this impacts your workflows.'
-			);
-		}
-		if (
-			debugConfiguration['debugAdapter'] === 'dlv-dap' &&
-			debugConfiguration.request === 'launch' &&
-			debugConfiguration['port']
-		) {
-			this.showWarning(
-				'ignorePortUsedInDlvDapWarning',
-				"`port` with 'dlv-dap' debugAdapter connects to [a `dlv dap` server](https://github.com/golang/vscode-go/wiki/debugging#run-debugee-externally) to launch a program or attach to a process. Remove 'host'/'port' from your launch.json configuration if you have not launched a 'dlv dap' server."
-			);
+		if (debugConfiguration['debugAdapter'] === 'dlv-dap') {
+			if (debugConfiguration['mode'] === 'remote') {
+				// This needs to use dlv at version 'v1.7.3-0.20211026171155-b48ceec161d5' or later,
+				// but we have no way of detectng that with an external server ahead of time.
+				// If an earlier version is used, the attach will fail with  warning about versions.
+			} else if (debugConfiguration['port']) {
+				this.showWarning(
+					'ignorePortUsedInDlvDapWarning',
+					"`port` with 'dlv-dap' debugAdapter connects to [an external `dlv dap` server](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#running-debugee-externally) to launch a program or attach to a process. Remove 'host' and 'port' from your launch.json if you have not launched a 'dlv dap' server."
+				);
+			}
 		}
 
 		const debugAdapter = debugConfiguration['debugAdapter'] === 'dlv-dap' ? 'dlv-dap' : 'dlv';
diff --git a/extension/src/goMain.ts b/extension/src/goMain.ts
index 73265bd..4f213f0 100644
--- a/extension/src/goMain.ts
+++ b/extension/src/goMain.ts
@@ -76,12 +76,24 @@
 
 const goCtx: GoExtensionContext = {};
 
-export async function activate(ctx: vscode.ExtensionContext): Promise<ExtensionAPI | undefined> {
-	if (process.env['VSCODE_GO_IN_TEST'] === '1') {
-		// Make sure this does not run when running in test.
-		return;
-	}
+// Allow tests to access the extension context utilities.
+interface ExtensionTestAPI {
+	globalState: vscode.Memento;
+}
 
+export async function activate(ctx: vscode.ExtensionContext): Promise<ExtensionAPI | ExtensionTestAPI | undefined> {
+	if (process.env['VSCODE_GO_IN_TEST'] === '1') {
+		// TODO: VSCODE_GO_IN_TEST was introduced long before we learned about
+		// ctx.extensionMode, and used in multiple places.
+		// Investigate if use of VSCODE_GO_IN_TEST can be removed
+		// in favor of ctx.extensionMode and clean up.
+		if (ctx.extensionMode === vscode.ExtensionMode.Test) {
+			return { globalState: ctx.globalState };
+		}
+		// We shouldn't expose the memento in production mode even when VSCODE_GO_IN_TEST
+		// environment variable is set.
+		return; // Skip the remaining activation work.
+	}
 	const start = Date.now();
 	setGlobalState(ctx.globalState);
 	setWorkspaceState(ctx.workspaceState);
diff --git a/extension/src/goTelemetry.ts b/extension/src/goTelemetry.ts
index 081a5aa..f37bd39 100644
--- a/extension/src/goTelemetry.ts
+++ b/extension/src/goTelemetry.ts
@@ -21,6 +21,25 @@
 // Exported for testing.
 export const TELEMETRY_START_TIME_KEY = 'telemetryStartTime';
 
+// Run our encode/decode function for the Date object, to be defensive
+// from vscode Memento API behavior change.
+// Exported for testing.
+export function recordTelemetryStartTime(storage: vscode.Memento, date: Date) {
+	storage.update(TELEMETRY_START_TIME_KEY, date.toJSON());
+}
+
+function readTelemetryStartTime(storage: vscode.Memento): Date | null {
+	const value = storage.get<string | number | Date>(TELEMETRY_START_TIME_KEY);
+	if (!value) {
+		return null;
+	}
+	const telemetryStartTime = new Date(value);
+	if (telemetryStartTime.toString() === 'Invalid Date') {
+		return null;
+	}
+	return telemetryStartTime;
+}
+
 enum ReporterState {
 	NOT_INITIALIZED,
 	IDLE,
@@ -153,9 +172,9 @@
 		this.active = true;
 		// record the first time we see the gopls with telemetry support.
 		// The timestamp will be used to avoid prompting too early.
-		const telemetryStartTime = globalState.get<Date>(TELEMETRY_START_TIME_KEY);
+		const telemetryStartTime = readTelemetryStartTime(globalState);
 		if (!telemetryStartTime) {
-			globalState.update(TELEMETRY_START_TIME_KEY, new Date());
+			recordTelemetryStartTime(globalState, new Date());
 		}
 	}
 
@@ -172,9 +191,11 @@
 		if (!isVSCodeTelemetryEnabled) return;
 
 		// Allow at least 7days for gopls to collect some data.
-		const now = new Date();
-		const telemetryStartTime = this.globalState.get<Date>(TELEMETRY_START_TIME_KEY, now);
-		if (daysBetween(telemetryStartTime, now) < 7) {
+		const telemetryStartTime = readTelemetryStartTime(this.globalState);
+		if (!telemetryStartTime) {
+			return;
+		}
+		if (daysBetween(telemetryStartTime, new Date()) < 7) {
 			return;
 		}
 
diff --git a/extension/src/language/legacy/goFormat.ts b/extension/src/language/legacy/goFormat.ts
index 8d8164d..692e221 100644
--- a/extension/src/language/legacy/goFormat.ts
+++ b/extension/src/language/legacy/goFormat.ts
@@ -12,7 +12,7 @@
 import { getGoConfig } from '../../config';
 import { toolExecutionEnvironment } from '../../goEnv';
 import { promptForMissingTool, promptForUpdatingTool } from '../../goInstallTools';
-import { getBinPath } from '../../util';
+import { getBinPath, resolvePath } from '../../util';
 import { killProcessTree } from '../../utils/processUtils';
 
 export class GoDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider {
@@ -144,7 +144,7 @@
 		return 'goimports';
 	}
 	if (formatTool === 'custom') {
-		return goConfig['alternateTools']['customFormatter'] || 'goimports';
+		return resolvePath(goConfig['alternateTools']['customFormatter'] || 'goimports');
 	}
 	return formatTool;
 }
diff --git a/extension/src/welcome.ts b/extension/src/welcome.ts
index b0afe12..d68ed75 100644
--- a/extension/src/welcome.ts
+++ b/extension/src/welcome.ts
@@ -12,7 +12,7 @@
 import semver = require('semver');
 import { extensionId } from './const';
 import { GoExtensionContext } from './context';
-import { extensionInfo } from './config';
+import { extensionInfo, getGoConfig } from './config';
 import { getFromGlobalState, updateGlobalState } from './stateUtils';
 import { createRegisterCommand } from './commands';
 
@@ -29,11 +29,7 @@
 				}
 			});
 		}
-
-		// Show the Go welcome page on update.
-		if (!extensionInfo.isInCloudIDE && vscode.workspace.getConfiguration('go.showWelcome')) {
-			showGoWelcomePage();
-		}
+		showGoWelcomePage();
 	}
 
 	public static currentPanel: WelcomePanel | undefined;
@@ -278,6 +274,9 @@
 }
 
 export function shouldShowGoWelcomePage(showVersions: string[], newVersion: string, oldVersion: string): boolean {
+	if (!extensionInfo.isInCloudIDE && getGoConfig().get('showWelcome') === false) {
+		return false;
+	}
 	if (newVersion === oldVersion) {
 		return false;
 	}
diff --git a/extension/test/gopls/extension.test.ts b/extension/test/gopls/extension.test.ts
index ee0facb..69d1eb5 100644
--- a/extension/test/gopls/extension.test.ts
+++ b/extension/test/gopls/extension.test.ts
@@ -10,7 +10,7 @@
 import { getGoConfig } from '../../src/config';
 import sinon = require('sinon');
 import { getGoVersion, GoVersion } from '../../src/util';
-import { GOPLS_MAYBE_PROMPT_FOR_TELEMETRY, TELEMETRY_START_TIME_KEY, TelemetryService } from '../../src/goTelemetry';
+import { GOPLS_MAYBE_PROMPT_FOR_TELEMETRY, recordTelemetryStartTime, TelemetryService } from '../../src/goTelemetry';
 import { MockMemento } from '../mocks/MockMemento';
 import { Env } from './goplsTestEnv.utils';
 
@@ -205,8 +205,7 @@
 		const workspaceDir = path.resolve(testdataDir, 'gogetdocTestData');
 		await env.startGopls(path.join(workspaceDir, 'test.go'), undefined, workspaceDir);
 		const memento = new MockMemento();
-		memento.update(TELEMETRY_START_TIME_KEY, new Date('2000-01-01'));
-
+		recordTelemetryStartTime(memento, new Date('2000-01-01'));
 		const sut = new TelemetryService(env.languageClient, memento, [GOPLS_MAYBE_PROMPT_FOR_TELEMETRY]);
 		try {
 			await Promise.all([
diff --git a/extension/test/gopls/telemetry.test.ts b/extension/test/gopls/telemetry.test.ts
index 32dc1fc..5d09301 100644
--- a/extension/test/gopls/telemetry.test.ts
+++ b/extension/test/gopls/telemetry.test.ts
@@ -10,7 +10,8 @@
 	GOPLS_MAYBE_PROMPT_FOR_TELEMETRY,
 	TELEMETRY_START_TIME_KEY,
 	TelemetryReporter,
-	TelemetryService
+	TelemetryService,
+	recordTelemetryStartTime
 } from '../../src/goTelemetry';
 import { MockMemento } from '../mocks/MockMemento';
 import { maybeInstallVSCGO } from '../../src/goInstallTools';
@@ -21,9 +22,12 @@
 import { rmdirRecursive } from '../../src/util';
 import { extensionId } from '../../src/const';
 import { executableFileExists, fileExists } from '../../src/utils/pathUtils';
-import { ExtensionMode } from 'vscode';
+import { ExtensionMode, Memento, extensions } from 'vscode';
 
-describe('# prompt for telemetry', () => {
+describe('# prompt for telemetry', async () => {
+	const extension = extensions.getExtension(extensionId);
+	assert(extension);
+
 	it(
 		'do not prompt if language client is not used',
 		testTelemetryPrompt(
@@ -131,6 +135,22 @@
 			false
 		)
 	);
+	// testExtensionAPI.globalState is a real memento instance passed by ExtensionHost.
+	// This instance is active throughout the integration test.
+	// When you add more test cases that interact with the globalState,
+	// be aware that multiple test cases may access and mutate it asynchronously.
+	const testExtensionAPI = await extension.activate();
+	it('check we can salvage the value in the real memento', async () => {
+		// write Date with Memento.update - old way. Now we always use string for TELEMETRY_START_TIME_KEY value.
+		testExtensionAPI.globalState.update(TELEMETRY_START_TIME_KEY, new Date(Date.now() - 7 * 24 * 60 * 60 * 1000));
+		await testTelemetryPrompt(
+			{
+				samplingInterval: 1000,
+				mementoInstance: testExtensionAPI.globalState
+			},
+			true
+		)();
+	});
 });
 
 interface testCase {
@@ -141,6 +161,7 @@
 	vsTelemetryDisabled?: boolean; // assume the user disabled vscode general telemetry.
 	samplingInterval: number; // N where N out of 1000 are sampled.
 	hashMachineID?: number; // stub the machine id hash computation function.
+	mementoInstance?: Memento; // if set, use this instead of mock memento.
 }
 
 function testTelemetryPrompt(tc: testCase, wantPrompt: boolean) {
@@ -153,9 +174,9 @@
 		const spy = sinon.spy(languageClient, 'sendRequest');
 		const lc = tc.noLangClient ? undefined : languageClient;
 
-		const memento = new MockMemento();
+		const memento = tc.mementoInstance ?? new MockMemento();
 		if (tc.firstDate) {
-			memento.update(TELEMETRY_START_TIME_KEY, tc.firstDate);
+			recordTelemetryStartTime(memento, tc.firstDate);
 		}
 		const commands = tc.goplsWithoutTelemetry ? [] : [GOPLS_MAYBE_PROMPT_FOR_TELEMETRY];
 
diff --git a/extension/test/integration/goDebug.test.ts b/extension/test/integration/goDebug.test.ts
index 6c86635..1c50eb7 100644
--- a/extension/test/integration/goDebug.test.ts
+++ b/extension/test/integration/goDebug.test.ts
@@ -42,10 +42,9 @@
 		name: 'Attach',
 		type: 'go',
 		request: 'attach',
-		mode: 'remote',
+		mode: 'remote', // This implies debugAdapter = legacy.
 		host: '127.0.0.1',
-		port: 3456,
-		debugAdapter: isDlvDap ? undefined : 'legacy'
+		port: 3456
 	};
 
 	let dc: DebugClient;
diff --git a/extension/test/integration/goDebugConfiguration.test.ts b/extension/test/integration/goDebugConfiguration.test.ts
index 3d6bff6..75ea818 100644
--- a/extension/test/integration/goDebugConfiguration.test.ts
+++ b/extension/test/integration/goDebugConfiguration.test.ts
@@ -951,7 +951,7 @@
 		assert.strictEqual(resolvedConfig['debugAdapter'], 'dlv-dap');
 	});
 
-	test("default debugAdapter for remote mode should be 'dlv-dap'", async () => {
+	test("default debugAdapter for remote mode should be 'legacy' when not in Preview mode", async () => {
 		const config = {
 			name: 'Attach',
 			type: 'go',
@@ -961,24 +961,24 @@
 			cwd: '/path'
 		};
 
-		const want = 'dlv-dap';
+		const want = extensionInfo.isPreview ? 'dlv-dap' : 'legacy';
 		await debugConfigProvider.resolveDebugConfiguration(undefined, config);
 		const resolvedConfig = config as any;
 		assert.strictEqual(resolvedConfig['debugAdapter'], want);
 	});
 
-	test('debugAdapter=legacy is allowed with remote mode', async () => {
+	test('debugAdapter=dlv-dap is allowed with remote mode', async () => {
 		const config = {
 			name: 'Attach',
 			type: 'go',
 			request: 'attach',
 			mode: 'remote',
-			debugAdapter: 'legacy',
+			debugAdapter: 'dlv-dap',
 			program: '/path/to/main_test.go',
 			cwd: '/path'
 		};
 
-		const want = 'legacy'; // If requested, legacy is preserved.
+		const want = 'dlv-dap'; // If requested, dlv-dap is preserved.
 		await debugConfigProvider.resolveDebugConfiguration(undefined, config);
 		const resolvedConfig = config as any;
 		assert.strictEqual(resolvedConfig['debugAdapter'], want);
diff --git a/extension/test/integration/welcome.test.ts b/extension/test/integration/welcome.test.ts
index 78afe71..72885ae 100644
--- a/extension/test/integration/welcome.test.ts
+++ b/extension/test/integration/welcome.test.ts
@@ -8,52 +8,70 @@
 import { shouldShowGoWelcomePage } from '../../src/welcome';
 import { extensionId } from '../../src/const';
 import { WelcomePanel } from '../../src/welcome';
+import sinon = require('sinon');
+import * as config from '../../src/config';
+import { MockCfg } from '../mocks/MockCfg';
 
 suite('WelcomePanel Tests', () => {
-	// 0:showVersions, 1:newVersion, 2:oldVersion, 3:expected
-	type testCase = [string[], string, string, boolean];
+	let sandbox: sinon.SinonSandbox;
+	setup(() => {
+		sandbox = sinon.createSandbox();
+	});
+	teardown(() => sandbox.restore());
+
+	// 0:showVersions, 1:newVersion, 2:oldVersion, 3: showWelcome, 4:expected
+	//
+	// If showWelcome is false, then expected has to be false.
+	// Otherwise, expected is true if (and only if) newVersion occurs in showVersions
+	// and is newer than oldVersion (as semantic versions).
+	type testCase = [string[], string, string, boolean, boolean];
 	const testCases: testCase[] = [
-		[[], '0.22.0', '0.0.0', false],
-		[[], '0.22.0', '0.21.0', false],
-		[[], '0.22.0', '0.22.0-rc.1', false],
-		[[], '0.22.0', '0.22.0', false],
-		[[], '0.22.0', '0.23.0', false],
+		[[], '0.22.0', '0.0.0', true, false],
+		[[], '0.22.0', '0.21.0', true, false],
+		[[], '0.22.0', '0.22.0-rc.1', true, false],
+		[[], '0.22.0', '0.22.0', true, false],
+		[[], '0.22.0', '0.23.0', true, false],
 
-		[['0.22.0'], '0.22.0', '0.0.0', true],
-		[['0.22.0'], '0.22.0', '0.21.0-rc.1', true],
-		[['0.22.0'], '0.22.0', '0.21.0', true],
-		[['0.22.0'], '0.22.0', '0.22.0-rc.1', true],
-		[['0.22.0'], '0.22.0', '0.22.0', false],
-		[['0.22.0'], '0.22.0', '0.22.1', false],
-		[['0.22.0'], '0.22.0', '0.23.0', false],
-		[['0.22.0'], '0.22.0', '1.0.0', false],
-		[['0.22.0'], '0.22.0', '2021.1.100', false],
+		[['0.22.0'], '0.22.0', '0.0.0', true, true],
+		[['0.22.0'], '0.22.0', '0.0.0', false, false],
+		[['0.22.0'], '0.22.0', '0.21.0-rc.1', true, true],
+		[['0.22.0'], '0.22.0', '0.21.0', true, true],
+		[['0.22.0'], '0.22.0', '0.22.0-rc.1', true, true],
+		[['0.22.0'], '0.22.0', '0.22.0', true, false],
+		[['0.22.0'], '0.22.0', '0.22.1', true, false],
+		[['0.22.0'], '0.22.0', '0.23.0', true, false],
+		[['0.22.0'], '0.22.0', '1.0.0', true, false],
+		[['0.22.0'], '0.22.0', '2021.1.100', true, false],
 
-		[['0.22.0'], '0.22.0-rc.2', '0.0.0', true],
-		[['0.22.0'], '0.22.0-rc.2', '0.21.0-rc.1', true],
-		[['0.22.0'], '0.22.0-rc.2', '0.21.0', true],
-		[['0.22.0'], '0.22.0-rc.2', '0.22.0-rc.1', true],
-		[['0.22.0'], '0.22.0-rc.2', '0.22.0-rc.2', false],
-		[['0.22.0'], '0.22.0-rc.2', '0.22.0-rc.3', true],
-		[['0.22.0'], '0.22.0-rc.2', '0.22.0', true],
-		[['0.22.0'], '0.22.0-rc.2', '0.22.1', false],
-		[['0.22.0'], '0.22.0-rc.2', '0.23.0', false],
-		[['0.22.0'], '0.22.0-rc.2', '1.0.0', false],
-		[['0.22.0'], '0.22.0-rc.2', '2021.1.100', false],
+		[['0.22.0'], '0.22.0-rc.2', '0.0.0', true, true],
+		[['0.22.0'], '0.22.0-rc.2', '0.21.0-rc.1', true, true],
+		[['0.22.0'], '0.22.0-rc.2', '0.21.0', true, true],
+		[['0.22.0'], '0.22.0-rc.2', '0.22.0-rc.1', true, true],
+		[['0.22.0'], '0.22.0-rc.2', '0.22.0-rc.2', true, false],
+		[['0.22.0'], '0.22.0-rc.2', '0.22.0-rc.3', true, true],
+		[['0.22.0'], '0.22.0-rc.2', '0.22.0', true, true],
+		[['0.22.0'], '0.22.0-rc.2', '0.22.1', true, false],
+		[['0.22.0'], '0.22.0-rc.2', '0.23.0', true, false],
+		[['0.22.0'], '0.22.0-rc.2', '1.0.0', true, false],
+		[['0.22.0'], '0.22.0-rc.2', '2021.1.100', true, false],
 
-		[['0.22.0'], '0.22.1', '0.0.0', false],
-		[['0.22.0'], '0.22.1', '0.21.0-rc.1', false],
-		[['0.22.0'], '0.22.1', '0.21.0', false],
-		[['0.22.0'], '0.22.1', '0.22.0-rc.1', false],
-		[['0.22.0'], '0.22.1', '0.22.0', false],
-		[['0.22.0'], '0.22.1', '0.23.0', false],
-		[['0.22.0'], '0.22.1', '1.0.0', false],
-		[['0.22.0'], '0.22.1', '2021.1.100', false]
+		[['0.22.0'], '0.22.1', '0.0.0', true, false],
+		[['0.22.0'], '0.22.1', '0.21.0-rc.1', true, false],
+		[['0.22.0'], '0.22.1', '0.21.0', true, false],
+		[['0.22.0'], '0.22.1', '0.22.0-rc.1', true, false],
+		[['0.22.0'], '0.22.1', '0.22.0', true, false],
+		[['0.22.0'], '0.22.1', '0.23.0', true, false],
+		[['0.22.0'], '0.22.1', '1.0.0', true, false],
+		[['0.22.0'], '0.22.1', '2021.1.100', true, false]
 	];
 	testCases.forEach((c: testCase) => {
-		const [showVersions, newVersion, oldVersion, expected] = c;
-
-		test(`shouldShowGoWelcomePage(${JSON.stringify(showVersions)}, ${newVersion}, ${oldVersion})`, () => {
+		const [showVersions, newVersion, oldVersion, showWelcome, expected] = c;
+		test(`shouldShowGoWelcomePage(${JSON.stringify(
+			showVersions
+		)}, ${newVersion}, ${oldVersion}, (showWelcome=${showWelcome}))`, () => {
+			const goConfig = new MockCfg([]);
+			sandbox.stub(config, 'getGoConfig').returns(goConfig);
+			sinon.stub(goConfig, 'get').withArgs('showWelcome').returns(showWelcome);
 			assert.strictEqual(shouldShowGoWelcomePage(showVersions, newVersion, oldVersion), expected);
 		});
 	});