Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Bug Fixes:
- Fix "Makefile Tools" being translated in localized languages when it should remain as a proper noun. [#731](https://github.com/microsoft/vscode-makefile-tools/issues/731)
- Fix binary path links in output log not being clickable on Windows when the path has no .exe extension. [#698](https://github.com/microsoft/vscode-makefile-tools/issues/698)
- Fix `parseCompilerArgs.bat` failing when macro definitions contain numeric expressions like `-1` inside quoted values. [#755](https://github.com/microsoft/vscode-makefile-tools/issues/755)
- Fix launch target resetting to "Unset" when `binaryArgs` is added to `makefile.launchConfigurations` on Windows. [#737](https://github.com/microsoft/vscode-makefile-tools/issues/737)
- Fix Project Outline showing "Unset" for launch configurations that have a `name` field. [#808](https://github.com/microsoft/vscode-makefile-tools/issues/808)

## 0.11
Expand Down
12 changes: 10 additions & 2 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -912,7 +912,12 @@ export async function setCurrentLaunchConfiguration(

function getLaunchConfiguration(name: string): LaunchConfiguration | undefined {
return launchConfigurations.find((k) => {
if (launchConfigurationToString(k) === name) {
if (
util.areLaunchConfigurationStringsEqual(
launchConfigurationToString(k),
name
)
) {
return { ...k, keep: true };
}
});
Expand Down Expand Up @@ -2838,7 +2843,10 @@ export async function selectLaunchConfiguration(): Promise<void> {
getCurrentLaunchConfiguration();
if (
!currentLaunchConfiguration ||
chosenId !== launchConfigurationToString(currentLaunchConfiguration)
!util.areLaunchConfigurationStringsEqual(
chosenId,
launchConfigurationToString(currentLaunchConfiguration)
)
) {
let telemetryProperties: telemetry.Properties | null = {
state: "launchConfiguration",
Expand Down
79 changes: 79 additions & 0 deletions src/test/fakeSuite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,85 @@ suite("Configuration settings", () => {
});
});

suite("Launch configuration string comparison", () => {
test("areLaunchConfigurationStringsEqual - same strings", () => {
const str1 = "c:\\Users\\test\\project>out()";
const str2 = "c:\\Users\\test\\project>out()";
expect(util.areLaunchConfigurationStringsEqual(str1, str2)).to.be.true;
});

test("areLaunchConfigurationStringsEqual - different strings", () => {
const str1 = "c:\\Users\\test\\project>out()";
const str2 = "c:\\Users\\test\\project>out(arg1)";
expect(util.areLaunchConfigurationStringsEqual(str1, str2)).to.be.false;
});

test("areLaunchConfigurationStringsEqual - same strings with args", () => {
const str1 = "c:\\Users\\test\\project>out(arg1,arg2)";
const str2 = "c:\\Users\\test\\project>out(arg1,arg2)";
expect(util.areLaunchConfigurationStringsEqual(str1, str2)).to.be.true;
});

test("areLaunchConfigurationStringsEqual - handles null/undefined", () => {
expect(util.areLaunchConfigurationStringsEqual(null as any, null as any)).to
.be.true;
expect(
util.areLaunchConfigurationStringsEqual(undefined as any, undefined as any)
).to.be.true;
expect(
util.areLaunchConfigurationStringsEqual(
"c:\\Users\\test\\project>out()",
null as any
)
).to.be.false;
expect(
util.areLaunchConfigurationStringsEqual(
null as any,
"c:\\Users\\test\\project>out()"
)
).to.be.false;
});

// When the string doesn't match the expected cwd>binary(args) format,
// the function falls back to case-insensitive comparison on Windows.
if (process.platform === "win32") {
test("areLaunchConfigurationStringsEqual - fallback for non-standard format on Windows", () => {
const str1 = "some-random-string";
const str2 = "Some-Random-String";
expect(util.areLaunchConfigurationStringsEqual(str1, str2)).to.be.true;
});

test("areLaunchConfigurationStringsEqual - fallback mismatch for non-standard format on Windows", () => {
const str1 = "some-random-string";
const str2 = "different-string";
expect(util.areLaunchConfigurationStringsEqual(str1, str2)).to.be.false;
});
}

// On Windows, paths are case-insensitive but arguments are case-sensitive
if (process.platform === "win32") {
test("areLaunchConfigurationStringsEqual - different path case on Windows", () => {
const str1 = "c:\\Users\\test\\project>out()";
const str2 = "C:\\Users\\Test\\Project>OUT()";
expect(util.areLaunchConfigurationStringsEqual(str1, str2)).to.be.true;
});

test("areLaunchConfigurationStringsEqual - different path case same args on Windows", () => {
const str1 = "c:\\users\\test\\project>out(arg1,arg2)";
const str2 = "C:\\Users\\TEST\\Project>Out(arg1,arg2)";
// Paths are compared case-insensitively, args are case-sensitive
expect(util.areLaunchConfigurationStringsEqual(str1, str2)).to.be.true;
});

test("areLaunchConfigurationStringsEqual - different args case on Windows", () => {
const str1 = "c:\\users\\test\\project>out(arg1,arg2)";
const str2 = "C:\\Users\\TEST\\Project>Out(Arg1,Arg2)";
// Arguments are case-sensitive even on Windows
expect(util.areLaunchConfigurationStringsEqual(str1, str2)).to.be.false;
});
}
});

// TODO: refactor initialization and cleanup of each test
suite("Fake dryrun parsing", () => {
suiteSetup(async function (this: Mocha.Context) {
Expand Down
43 changes: 43 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,49 @@ export function hasProperties(obj: any): boolean {
return props && props.length > 0;
}

// Helper for comparing launch configuration strings.
// On Windows, paths are case-insensitive, so we need to do a case-insensitive comparison
// for the path portions (cwd and binaryPath), while keeping the arguments case-sensitive.
// Launch configuration string format: [CWD path]>[binaryPath]([binaryArg1,binaryArg2,...])
export function areLaunchConfigurationStringsEqual(
str1: string,
str2: string
): boolean {
// Handle null/undefined inputs
if (str1 === null || str1 === undefined) {
return str2 === null || str2 === undefined;
}
if (str2 === null || str2 === undefined) {
return false;
}

if (process.platform !== "win32") {
return str1 === str2;
}

// On Windows, parse the launch configuration strings to compare
// paths case-insensitively while keeping arguments case-sensitive.
// Format: [CWD path]>[binaryPath]([binaryArg1,binaryArg2,...])
const regexp: RegExp = /^(.*)\>(.*)\((.*)\)$/;
const match1: RegExpExecArray | null = regexp.exec(str1);
const match2: RegExpExecArray | null = regexp.exec(str2);

// If either string doesn't match the expected format, fall back to case-insensitive comparison
if (!match1 || !match2) {
return str1.toLowerCase() === str2.toLowerCase();
}

// Compare cwd and binaryPath case-insensitively, and args case-sensitively
const cwd1: string = match1[1].toLowerCase();
const cwd2: string = match2[1].toLowerCase();
const binPath1: string = match1[2].toLowerCase();
const binPath2: string = match2[2].toLowerCase();
const args1: string = match1[3];
const args2: string = match2[3];

return cwd1 === cwd2 && binPath1 === binPath2 && args1 === args2;
}

// Apply any properties from source to destination, logging for overwrite.
// To make things simpler for the caller, create a valid dst if given null or undefined.
export function mergeProperties(dst: any, src: any): any {
Expand Down
Loading