Skip to content
Draft
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
12 changes: 10 additions & 2 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,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 @@ -2661,7 +2666,10 @@ export async function selectLaunchConfiguration(): Promise<void> {
getCurrentLaunchConfiguration();
if (
!currentLaunchConfiguration ||
chosen !== launchConfigurationToString(currentLaunchConfiguration)
!util.areLaunchConfigurationStringsEqual(
chosen,
launchConfigurationToString(currentLaunchConfiguration)
)
) {
let telemetryProperties: telemetry.Properties | null = {
state: "launchConfiguration",
Expand Down
63 changes: 63 additions & 0 deletions src/test/fakeSuite/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,69 @@ suite("Unit testing replacing characters in and outside of quotes", () => {
});
});

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;
});

// 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 @@ -858,6 +858,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