extension/src: use full path mode when running go test
The fullpath mode is added in go 1.21 and the vscode-go is supporting
the last three minor Go versions (1.23.0+ as of 2025-09).
If the user is beyond the support, the extension will send a
notification asking user to upgrade.
In case user does not want to upgrade, set "-test.fullpath=false"
to setting "go.testFlags" will fall back to the original behavior.
Fix golang/vscode-go#3853
Change-Id: I06b723b715651ea0bf98ae62797917af7b1c74b2
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/704776
Reviewed-by: Madeline Kalil <mkalil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47b8226..761ea99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,19 @@
## Unreleased
+### Important
+
+* To ensure the extension remains fully compatible and stable, the required
+minimum Go version remains Go 1.23. A new notification will now be sent to help
+users running older versions upgrade to Go 1.23+.
+
+### Fixes
+
+* Corrected an issue where clicking on a failing test in the Test Explorer would
+open a non-existent Go file (golang/vscode-go#3853). This occurred when the test
+entry point (e.g., .../foo_test.go) was in a different directory than the file
+where the failure actually occurred (e.g., .../bar/bar_test.go).
+
## v0.51.0 (prerelease)
Date: 2025-09-04
diff --git a/extension/src/goEnvironmentStatus.ts b/extension/src/goEnvironmentStatus.ts
index babec5d..31d5d8d 100644
--- a/extension/src/goEnvironmentStatus.ts
+++ b/extension/src/goEnvironmentStatus.ts
@@ -13,6 +13,7 @@
import os = require('os');
import path = require('path');
import { promisify } from 'util';
+import semverCoerce from 'semver/functions/coerce';
import { getGoConfig, extensionInfo } from './config';
import { toolInstallationEnvironment } from './goEnv';
import { addGoStatus, goEnvStatusbarItem, outputChannel, removeGoStatus } from './goStatus';
@@ -549,7 +550,12 @@
}
const STATUS_BAR_ITEM_NAME = 'Go Update Notification';
-const dismissedGoVersionUpdatesKey = 'dismissedGoVersionUpdates';
+
+/**
+ * Key for the global state that tracks Go versions for which the user has
+ * explicitly dismissed the upgrade notification.
+ */
+const DISMISSED_GO_VERSION_KEY = 'dismissedGoVersionUpdates';
export async function offerToInstallLatestGoVersion(ctx: Pick<vscode.ExtensionContext, 'subscriptions'>) {
if (extensionInfo.isInCloudIDE) {
@@ -564,83 +570,96 @@
return;
}
- let options = await getLatestGoVersions();
+ let latestVersions = await getLatestGoVersions();
- // Filter out Go versions the user has already dismissed.
- let dismissedOptions: GoEnvironmentOption[];
- dismissedOptions = await getFromGlobalState(dismissedGoVersionUpdatesKey);
- if (dismissedOptions) {
- options = options.filter((version) => !dismissedOptions.find((x) => x.label === version.label));
+ const currentVersion = await getGoVersion();
+
+ // The official support for vscode-go is last three minor versions of Go.
+ // Try to start with last four minor versions.
+ let minimumVersion = semverCoerce(latestVersions[0].label);
+ if (minimumVersion) {
+ minimumVersion.minor = minimumVersion.minor - 3;
+ minimumVersion.patch = 0;
}
- // Compare to current go version.
- const currentVersion = await getGoVersion();
+ const download = {
+ title: 'Download',
+ async command() {
+ await vscode.env.openExternal(vscode.Uri.parse('https://go.dev/dl/'));
+ }
+ };
+
+ // Popup if the Go version is beyond support.
+ if (minimumVersion && currentVersion.lt(minimumVersion.format())) {
+ let text = `The minimum supported Go version is ${minimumVersion.format()}. Please update your Go to ensure the extension functions correctly. You are currently using ${formatGoVersion(
+ currentVersion
+ )}.`;
+ vscode.window.showInformationMessage(text, download).then((selection) => {
+ selection?.command();
+ });
+ }
+
+ // Filter out Go versions the user has already dismissed.
+ const dismissedVersions: GoEnvironmentOption[] = await getFromGlobalState(DISMISSED_GO_VERSION_KEY);
+ if (dismissedVersions) {
+ latestVersions = latestVersions.filter((version) => !dismissedVersions.find((x) => x.label === version.label));
+ }
+
+ // Filter out Go versions below the current go versions.
if (currentVersion) {
- options = options.filter((version) => currentVersion.lt(version.label));
+ latestVersions = latestVersions.filter((version) => currentVersion.lt(version.label));
}
// Notify user that there is a newer version of Go available.
- if (options.length > 0) {
- const versionsText = options.map((x) => x.label).join(', ');
- const statusBarItem = addGoStatus(STATUS_BAR_ITEM_NAME);
- statusBarItem.name = STATUS_BAR_ITEM_NAME;
- statusBarItem.text = 'New Go version is available';
- statusBarItem.detail = versionsText;
- statusBarItem.command = {
- title: 'Upgrade',
- command: 'go.promptforgoinstall',
- arguments: [options],
- tooltip: 'Upgrade or silence notification'
- };
- // TODO: Error level is more visible. Consider to make it configurable?
- statusBarItem.severity = vscode.LanguageStatusSeverity.Warning;
-
+ if (latestVersions.length > 0) {
ctx.subscriptions.push(
vscode.commands.registerCommand('go.promptforgoinstall', () => {
- const download = {
- title: 'Download',
- async command() {
- await vscode.env.openExternal(vscode.Uri.parse('https://go.dev/dl/'));
- }
- };
-
const neverAgain = {
title: "Don't Show Again",
async command() {
// Mark these versions as seen.
- dismissedOptions = await getFromGlobalState(dismissedGoVersionUpdatesKey);
- if (!dismissedOptions) {
- dismissedOptions = [];
+ let dismissedVersions: GoEnvironmentOption[] = await getFromGlobalState(
+ DISMISSED_GO_VERSION_KEY
+ );
+ if (!dismissedVersions) {
+ dismissedVersions = [];
}
- options.forEach((version) => {
- dismissedOptions.push(version);
+ latestVersions.forEach((version) => {
+ dismissedVersions.push(version);
});
- await updateGlobalState(dismissedGoVersionUpdatesKey, dismissedOptions);
+ await updateGlobalState(DISMISSED_GO_VERSION_KEY, dismissedVersions);
}
};
- let versionsText: string;
- if (options.length > 1) {
- versionsText = `${options
+ let text: string;
+ if (latestVersions.length > 1) {
+ text = `${latestVersions
.map((x) => x.label)
.reduce((prev, next) => {
return prev + ' and ' + next;
})} are available`;
} else {
- versionsText = `${options[0].label} is available`;
+ text = `${latestVersions[0].label} is available.`;
}
-
- vscode.window
- .showInformationMessage(
- `${versionsText}. You are currently using ${formatGoVersion(currentVersion)}.`,
- download,
- neverAgain
- )
- .then((selection) => {
- selection?.command();
- removeGoStatus(STATUS_BAR_ITEM_NAME);
- });
+ text = text + ` You are currently using ${formatGoVersion(currentVersion)}.`;
+ vscode.window.showInformationMessage(text, download, neverAgain).then((selection) => {
+ selection?.command();
+ removeGoStatus(STATUS_BAR_ITEM_NAME);
+ });
})
);
+
+ const statusBarItem = addGoStatus(STATUS_BAR_ITEM_NAME);
+ statusBarItem.name = STATUS_BAR_ITEM_NAME;
+ statusBarItem.text = 'New Go version is available';
+ statusBarItem.detail = latestVersions.map((x) => x.label).join(', ');
+ statusBarItem.command = {
+ title: 'Upgrade',
+ command: 'go.promptforgoinstall',
+ arguments: [latestVersions],
+ tooltip: 'Upgrade or silence notification'
+ };
+ // TODO: Error level is more visible. Consider to make it configurable?
+ statusBarItem.severity = vscode.LanguageStatusSeverity.Warning;
}
}
diff --git a/extension/src/goMain.ts b/extension/src/goMain.ts
index 52915d0..537d68e 100644
--- a/extension/src/goMain.ts
+++ b/extension/src/goMain.ts
@@ -45,7 +45,7 @@
import { GO111MODULE, goModInit } from './goModules';
import { playgroundCommand } from './goPlayground';
import { GoRunTestCodeLensProvider } from './goRunTestCodelens';
-import { disposeGoStatusBar, expandGoStatusBar, outputChannel, updateGoStatusBar } from './goStatus';
+import { disposeGoStatusBar, expandGoStatusBar, updateGoStatusBar } from './goStatus';
import { vetCode } from './goVet';
import {
@@ -75,7 +75,6 @@
import { GoTaskProvider } from './goTaskProvider';
import { setTelemetryEnvVars, activationLatency, telemetryReporter } from './goTelemetry';
import { experiments } from './experimental';
-import { allToolsInformation } from './goToolsInformation';
const goCtx: GoExtensionContext = {};
diff --git a/extension/src/testUtils.ts b/extension/src/testUtils.ts
index 4070f6e..72a185f 100644
--- a/extension/src/testUtils.ts
+++ b/extension/src/testUtils.ts
@@ -544,7 +544,8 @@
tmpCoverPath?: string; // coverage file path if coverage info is necessary.
addJSONFlag: boolean | undefined; // true if we add extra -json flag for stream processing.
} {
- const args: Array<string> = ['test'];
+ // By default, enable full path mode to address golang/vscode-go#3853.
+ const args: Array<string> = ['test', '-test.fullpath=true'];
// user-specified flags
const argsFlagIdx = testconfig.flags?.indexOf('-args') ?? -1;
const userFlags = argsFlagIdx < 0 ? testconfig.flags : testconfig.flags.slice(0, argsFlagIdx);
diff --git a/extension/test/integration/test.test.ts b/extension/test/integration/test.test.ts
index 500b176..34c1c19 100644
--- a/extension/test/integration/test.test.ts
+++ b/extension/test/integration/test.test.ts
@@ -42,57 +42,57 @@
test('default config', () => {
runTest({
- expectedArgs: 'test -timeout 30s ./...',
- expectedOutArgs: 'test -timeout 30s ./...'
+ expectedArgs: 'test -test.fullpath=true -timeout 30s ./...',
+ expectedOutArgs: 'test -test.fullpath=true -timeout 30s ./...'
});
});
test('user flag [-v] enables -json flag', () => {
runTest({
- expectedArgs: 'test -timeout 30s -json ./... -v',
- expectedOutArgs: 'test -timeout 30s ./... -v',
+ expectedArgs: 'test -test.fullpath=true -timeout 30s -json ./... -v',
+ expectedOutArgs: 'test -test.fullpath=true -timeout 30s ./... -v',
flags: ['-v']
});
});
test('user flag [-json -v] prevents -json flag addition', () => {
runTest({
- expectedArgs: 'test -timeout 30s ./... -json -v',
- expectedOutArgs: 'test -timeout 30s ./... -json -v',
+ expectedArgs: 'test -test.fullpath=true -timeout 30s ./... -json -v',
+ expectedOutArgs: 'test -test.fullpath=true -timeout 30s ./... -json -v',
flags: ['-json', '-v']
});
});
test('user flag [-args] does not crash', () => {
runTest({
- expectedArgs: 'test -timeout 30s ./... -args',
- expectedOutArgs: 'test -timeout 30s ./... -args',
+ expectedArgs: 'test -test.fullpath=true -timeout 30s ./... -args',
+ expectedOutArgs: 'test -test.fullpath=true -timeout 30s ./... -args',
flags: ['-args']
});
});
test('user flag [-args -v] does not enable -json flag', () => {
runTest({
- expectedArgs: 'test -timeout 30s ./... -args -v',
- expectedOutArgs: 'test -timeout 30s ./... -args -v',
+ expectedArgs: 'test -test.fullpath=true -timeout 30s ./... -args -v',
+ expectedOutArgs: 'test -test.fullpath=true -timeout 30s ./... -args -v',
flags: ['-args', '-v']
});
});
test('specifying functions adds -run flags', () => {
runTest({
- expectedArgs: 'test -timeout 30s -run ^(TestA|TestB)$ ./...',
- expectedOutArgs: 'test -timeout 30s -run ^(TestA|TestB)$ ./...',
+ expectedArgs: 'test -test.fullpath=true -timeout 30s -run ^(TestA|TestB)$ ./...',
+ expectedOutArgs: 'test -test.fullpath=true -timeout 30s -run ^(TestA|TestB)$ ./...',
functions: ['TestA', 'TestB']
});
});
test('functions & benchmark adds -bench flags and skips timeout', () => {
runTest({
- expectedArgs: 'test -benchmem -run=^$ -bench ^(TestA|TestB)$ ./...',
- expectedOutArgs: 'test -benchmem -run=^$ -bench ^(TestA|TestB)$ ./...',
+ expectedArgs: 'test -test.fullpath=true -benchmem -run=^$ -bench ^(TestA|TestB)$ ./...',
+ expectedOutArgs: 'test -test.fullpath=true -benchmem -run=^$ -bench ^(TestA|TestB)$ ./...',
functions: ['TestA', 'TestB'],
isBenchmark: true
});
});
test('user -run flag is ignored when functions are provided', () => {
runTest({
- expectedArgs: 'test -timeout 30s -run ^(TestA|TestB)$ ./...',
- expectedOutArgs: 'test -timeout 30s -run ^(TestA|TestB)$ ./...',
+ expectedArgs: 'test -test.fullpath=true -timeout 30s -run ^(TestA|TestB)$ ./...',
+ expectedOutArgs: 'test -test.fullpath=true -timeout 30s -run ^(TestA|TestB)$ ./...',
functions: ['TestA', 'TestB'],
flags: ['-run', 'TestC']
});
@@ -100,9 +100,9 @@
test('use -testify.m for methods', () => {
runTest({
expectedArgs:
- 'test -timeout 30s -run ^TestExampleTestSuite$ -testify.m ^(TestExample|TestAnotherExample)$ ./...',
+ 'test -test.fullpath=true -timeout 30s -run ^TestExampleTestSuite$ -testify.m ^(TestExample|TestAnotherExample)$ ./...',
expectedOutArgs:
- 'test -timeout 30s -run ^TestExampleTestSuite$ -testify.m ^(TestExample|TestAnotherExample)$ ./...',
+ 'test -test.fullpath=true -timeout 30s -run ^TestExampleTestSuite$ -testify.m ^(TestExample|TestAnotherExample)$ ./...',
functions: [
'(*ExampleTestSuite).TestExample',
'(*ExampleTestSuite).TestAnotherExample',