From d2da6e27df9f4e6809a5dd4e7047f51c3658d5a3 Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Fri, 16 Oct 2026 20:16:28 +0330 Subject: [PATCH 1/4] Add CppModulesProject test scenario for C++20 modules groundwork --- .github/workflows/job-compile-and-test.yml | 5 + .../CppModulesProject/assets/.gitignore | 3 + .../CppModulesProject/assets/bin/gcc | 0 .../CppModulesProject/assets/bin/gcc.exe | 0 .../CppModulesProject/assets/main.cpp | 13 + .../CppModulesProject/assets/main2.cpp | 10 + .../CppModulesProject/assets/main3.cpp | 10 + .../CppModulesProject/assets/math.cppm | 24 ++ .../CppModulesProject/assets/math.ixx | 13 + .../tests/modules.integration.test.ts | 300 ++++++++++++++++++ 10 files changed, 378 insertions(+) create mode 100644 Extension/test/scenarios/CppModulesProject/assets/.gitignore create mode 100644 Extension/test/scenarios/CppModulesProject/assets/bin/gcc create mode 100644 Extension/test/scenarios/CppModulesProject/assets/bin/gcc.exe create mode 100644 Extension/test/scenarios/CppModulesProject/assets/main.cpp create mode 100644 Extension/test/scenarios/CppModulesProject/assets/main2.cpp create mode 100644 Extension/test/scenarios/CppModulesProject/assets/main3.cpp create mode 100644 Extension/test/scenarios/CppModulesProject/assets/math.cppm create mode 100644 Extension/test/scenarios/CppModulesProject/assets/math.ixx create mode 100644 Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts diff --git a/.github/workflows/job-compile-and-test.yml b/.github/workflows/job-compile-and-test.yml index 3b693283b..0fd5f8064 100644 --- a/.github/workflows/job-compile-and-test.yml +++ b/.github/workflows/job-compile-and-test.yml @@ -106,6 +106,11 @@ jobs: run: yarn test --scenario=SimpleCppProject working-directory: Extension + - name: Run CppModulesProject tests (Windows) + if: ${{ inputs.platform == 'windows' }} + run: yarn test --scenario=CppModulesProject + working-directory: Extension + - name: Run E2E IntelliSense features tests (Windows) if: ${{ inputs.platform == 'windows' }} run: yarn test --scenario=MultirootDeadlockTest diff --git a/Extension/test/scenarios/CppModulesProject/assets/.gitignore b/Extension/test/scenarios/CppModulesProject/assets/.gitignore new file mode 100644 index 000000000..47001bd44 --- /dev/null +++ b/Extension/test/scenarios/CppModulesProject/assets/.gitignore @@ -0,0 +1,3 @@ +# Generated at runtime by the scenario tests. +.vscode/ +build/ diff --git a/Extension/test/scenarios/CppModulesProject/assets/bin/gcc b/Extension/test/scenarios/CppModulesProject/assets/bin/gcc new file mode 100644 index 000000000..e69de29bb diff --git a/Extension/test/scenarios/CppModulesProject/assets/bin/gcc.exe b/Extension/test/scenarios/CppModulesProject/assets/bin/gcc.exe new file mode 100644 index 000000000..e69de29bb diff --git a/Extension/test/scenarios/CppModulesProject/assets/main.cpp b/Extension/test/scenarios/CppModulesProject/assets/main.cpp new file mode 100644 index 000000000..58d63e77f --- /dev/null +++ b/Extension/test/scenarios/CppModulesProject/assets/main.cpp @@ -0,0 +1,13 @@ +// Module importer used by the compile_commands.json based tests. +// +// Diagnostics on the import statements are expected until IntelliSense +// support for C++20 modules is available for all compilers +// (https://github.com/microsoft/vscode-cpptools/issues/6302). + +import math; +import geometry; + +int main() +{ + return add(1, 2) + square_area(3) - math::k_answer; +} diff --git a/Extension/test/scenarios/CppModulesProject/assets/main2.cpp b/Extension/test/scenarios/CppModulesProject/assets/main2.cpp new file mode 100644 index 000000000..9e0f2347c --- /dev/null +++ b/Extension/test/scenarios/CppModulesProject/assets/main2.cpp @@ -0,0 +1,10 @@ +// Module importer used by the custom configuration provider tests +// (GCC-style module compiler arguments). + +import math; +import geometry; + +int main() +{ + return add(2, 3) + square_area(4) - math::k_answer; +} diff --git a/Extension/test/scenarios/CppModulesProject/assets/main3.cpp b/Extension/test/scenarios/CppModulesProject/assets/main3.cpp new file mode 100644 index 000000000..743a47db5 --- /dev/null +++ b/Extension/test/scenarios/CppModulesProject/assets/main3.cpp @@ -0,0 +1,10 @@ +// Module importer used by the custom configuration provider tests +// (MSVC-style module compiler arguments). + +import math; +import geometry; + +int main() +{ + return add(3, 4) + square_area(5) - math::k_answer; +} diff --git a/Extension/test/scenarios/CppModulesProject/assets/math.cppm b/Extension/test/scenarios/CppModulesProject/assets/math.cppm new file mode 100644 index 000000000..c64f69b97 --- /dev/null +++ b/Extension/test/scenarios/CppModulesProject/assets/math.cppm @@ -0,0 +1,24 @@ +// Module interface unit using the GCC-style file extension (.cppm). +// +// IntelliSense support for C++20 modules is tracked at +// https://github.com/microsoft/vscode-cpptools/issues/6302. These sources are +// used by the CppModulesProject scenario tests to cover the extension-side +// behavior that module based projects rely on. + +export module math; + +export int add(int a, int b) +{ + return a + b; +} + +export namespace math +{ + constexpr int k_answer = 42; +} + +// Not exported; part of the module implementation only. +int implementation_detail() +{ + return math::k_answer; +} diff --git a/Extension/test/scenarios/CppModulesProject/assets/math.ixx b/Extension/test/scenarios/CppModulesProject/assets/math.ixx new file mode 100644 index 000000000..66f122575 --- /dev/null +++ b/Extension/test/scenarios/CppModulesProject/assets/math.ixx @@ -0,0 +1,13 @@ +// Module interface unit using the MSVC-style file extension (.ixx). +// +// IntelliSense support for C++20 modules is tracked at +// https://github.com/microsoft/vscode-cpptools/issues/6302. These sources are +// used by the CppModulesProject scenario tests to cover the extension-side +// behavior that module based projects rely on. + +export module geometry; + +export int square_area(int side) +{ + return side * side; +} diff --git a/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts b/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts new file mode 100644 index 000000000..bae8a4745 --- /dev/null +++ b/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts @@ -0,0 +1,300 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +/* eslint-disable @typescript-eslint/triple-slash-reference */ +/// + +// Groundwork for IntelliSense support of C++20 modules importing +// (https://github.com/microsoft/vscode-cpptools/issues/6302). +// +// These tests cover the extension-side behavior that module based projects +// rely on today: +// * module interface files (.cppm/.ixx) are recognized as C++ sources, +// * a compile_commands.json that contains GCC module related flags does not +// prevent the language server from providing IntelliSense for the project, +// * module related compiler arguments (both GCC and MSVC spellings) are +// accepted and delivered through the custom configuration provider path. +// +// No statement is made about the semantic understanding of module imports by +// the language server itself: that support is still in development and is +// tracked by the issue referenced above. When it ships, these tests are the +// place to add end-to-end module IntelliSense assertions. + +import * as assert from 'assert'; +import { suite } from 'mocha'; +import * as vscode from 'vscode'; +import * as api from 'vscode-cpptools'; +import * as apit from 'vscode-cpptools/out/testApi'; +import * as config from '../../../../src/LanguageServer/configurations'; +import * as util from '../../../../src/common'; +import * as testHelpers from '../../../common/testHelpers'; + +/* **************************************************************************** */ + +function workspacePath(): string { + const folder = vscode.workspace.workspaceFolders?.[0]; + assert.ok(folder, 'workspace folder should be set.'); + + return folder.uri.fsPath; +} + +function toPosixPath(path: string): string { + return path.replace(/\\/g, '/'); +} + +// The (empty) fake gcc binaries are checked in with the scenario assets, +// following the SimpleCppProject pattern, so that the language server does not +// probe a real compiler and the tests stay deterministic across machines. +function fakeGccPath(): string { + return toPosixPath(`${workspacePath()}/bin/${process.platform === 'win32' ? 'gcc.exe' : 'gcc'}`); +} + +function compileCommandsPath(): string { + return `${workspacePath()}/build/compile_commands.json`; +} + +function cppPropertiesPath(): string { + return workspacePath() + "/.vscode/c_cpp_properties.json"; +} + +async function changeCppProperties(cppProperties: config.ConfigurationJson, _disposables: vscode.Disposable[]): Promise { + await util.writeFileText(cppPropertiesPath(), JSON.stringify(cppProperties)); + const contents: string = await util.readFileText(cppPropertiesPath()); + console.log(" wrote c_cpp_properties.json: " + contents); + + // Sleep for 4000ms for file watcher + return new Promise(r => setTimeout(r, 4000)); +} + +async function openAndAwaitIntelliSenseReady(cpptools: apit.CppToolsTestApi, disposables: vscode.Disposable[], filename: string): Promise { + const path: string = workspacePath() + "/" + filename; + + const testHook: apit.CppToolsTestHook = cpptools.getTestHook(); + const testResult: Promise = new Promise((resolve, reject) => { + disposables.push(testHook.IntelliSenseStatusChanged(result => { + result = result as apit.IntelliSenseStatus; + if (result.filename === filename && result.status === apit.Status.IntelliSenseReady) { + resolve(); + } + })); + setTimeout(() => { reject(new Error(`timeout waiting for IntelliSenseReady: ${filename}`)); }, testHelpers.defaultTimeout); + }); + disposables.push(testHook); + + const document: vscode.TextDocument = await vscode.workspace.openTextDocument(path); + await vscode.window.showTextDocument(document); + await testResult; + return document; +} + +/* **************************************************************************** */ + +interface CompileCommandEntry { + directory: string; + arguments: string[]; + file: string; +} + +// GCC spellings of the C++20 module related compiler flags, as emitted by +// CMake's C++ module support for GCC targets. +function gccModuleArgs(cmiDir: string): string[] { + return [ + "-std=c++20", + "-fmodules-ts", + `-fprebuilt-module-path=${cmiDir}`, + `-fmodule-file=math=${cmiDir}/math.gcm` + ]; +} + +function compileCommandEntry(file: string): CompileCommandEntry { + const root: string = toPosixPath(workspacePath()); + const objectFile: string = `${root}/build/${file.replace(/\.[^.]+$/, "")}.o`; + return { + directory: root, + arguments: [fakeGccPath(), ...gccModuleArgs(`${root}/build/cmi`), "-c", "-o", objectFile, file], + file: file + }; +} + +async function writeCompileCommands(entries: CompileCommandEntry[]): Promise { + await util.writeFileText(compileCommandsPath(), JSON.stringify(entries, null, 2)); + console.log(" wrote build/compile_commands.json for: " + entries.map(entry => entry.file).join(", ")); +} + +/* **************************************************************************** */ + +// MSVC spellings of the C++20 module related compiler arguments, as used by +// the (currently MSVC-only) modules IntelliSense workaround. +function msvcModuleArgs(): string[] { + const ifcDir: string = `${toPosixPath(workspacePath())}/build/ifc`; + return [ + "/ifcSearchDir", + ifcDir, + "/reference", + `math=${ifcDir}/math.ifc`, + "/headerUnit", + `std=${ifcDir}/std.ifc`, + "/stdIfcDir", + ifcDir + ]; +} + +/* **************************************************************************** */ + +suite("C++20 modules file type tests", function(): void { + suiteSetup(async function(): Promise { + await testHelpers.activateCppExtension(); + }); + + test("Module interface files are opened as C++ (.cppm)", async () => { + const document: vscode.TextDocument = await vscode.workspace.openTextDocument(workspacePath() + "/math.cppm"); + await vscode.window.showTextDocument(document); + assert.strictEqual(document.languageId, 'cpp'); + }); + + test("Module interface files are opened as C++ (.ixx)", async () => { + const document: vscode.TextDocument = await vscode.workspace.openTextDocument(workspacePath() + "/math.ixx"); + await vscode.window.showTextDocument(document); + assert.strictEqual(document.languageId, 'cpp'); + }); +}); + +/* **************************************************************************** */ + +suite("compile_commands.json with GCC C++20 module flags", function(): void { + let cpptools: apit.CppToolsTestApi; + const disposables: vscode.Disposable[] = []; + + suiteSetup(async function(): Promise { + cpptools = await apit.getCppToolsTestApi(api.Version.v2) ?? assert.fail('Could not get CppToolsTestApi'); + disposables.push(cpptools); + + await writeCompileCommands([ + compileCommandEntry("main.cpp"), + compileCommandEntry("math.cppm"), + compileCommandEntry("math.ixx") + ]); + + await changeCppProperties({ + configurations: [ + { + name: "GCC C++20 modules", + compileCommands: [compileCommandsPath()], + intelliSenseMode: "gcc-x64", + cppStandard: "c++20" + } + ], + version: 4 + }, + disposables); + }); + + suiteTeardown(function(): void { + disposables.forEach(d => d.dispose()); + }); + + test("IntelliSense is provided for the module importer (main.cpp)", async () => { + await openAndAwaitIntelliSenseReady(cpptools, disposables, "main.cpp"); + }); +}); + +/* **************************************************************************** */ + +suite("custom configuration provider with module compiler args", function(): void { + let cpptools: apit.CppToolsTestApi; + let lastResult: api.SourceFileConfigurationItem[]; + let configurationProvidedBeforeCanProvide: boolean = false; + const supportedUris: Set = new Set(); + const disposables: vscode.Disposable[] = []; + + // gcc-style module args for main2.cpp, MSVC-style module args for main3.cpp. + const gccModuleConfig: api.SourceFileConfiguration = { + includePath: [ "${workspaceFolder}" ], + defines: [], + intelliSenseMode: "gcc-x64", + standard: "c++20", + compilerArgs: gccModuleArgs(`${toPosixPath(workspacePath())}/build/cmi`) + }; + const msvcModuleConfig: api.SourceFileConfiguration = { + includePath: [ "${workspaceFolder}" ], + defines: [], + intelliSenseMode: "msvc-x64", + standard: "c++20", + compilerArgs: msvcModuleArgs() + }; + + const provider: api.CustomConfigurationProvider = { + name: "cpptoolsTest-modules", + extensionId: "ms-vscode.cpptools-test-modules", + canProvideConfiguration(document: vscode.Uri): Thenable { + supportedUris.add(document.toString()); + return Promise.resolve(true); + }, + provideConfigurations(uris: vscode.Uri[]): Thenable { + const result: api.SourceFileConfigurationItem[] = []; + uris.forEach(uri => { + const uriString: string = uri.toString(); + if (!supportedUris.has(uriString)) { + configurationProvidedBeforeCanProvide = true; + } + const configuration: api.SourceFileConfiguration = uri.path.endsWith("main3.cpp") ? msvcModuleConfig : gccModuleConfig; + result.push({ + uri: uriString, + configuration: configuration + }); + }); + lastResult = result; + return Promise.resolve(result); + }, + dispose(): void { + console.log(" disposed"); + }, + canProvideBrowseConfiguration(): Thenable { + return Promise.resolve(false); + }, + provideBrowseConfiguration(): Thenable { + return Promise.reject(new Error("no browse configuration")); + }, + canProvideBrowseConfigurationsPerFolder(): Thenable { + return Promise.resolve(false); + }, + provideFolderBrowseConfiguration(_uri: vscode.Uri): Thenable { + return Promise.reject(new Error("no folder browse configuration")); + } + }; + + suiteSetup(async function(): Promise { + cpptools = await apit.getCppToolsTestApi(api.Version.v6) ?? assert.fail('Could not get CppToolsTestApi'); + cpptools.registerCustomConfigurationProvider(provider); + cpptools.notifyReady(provider); + disposables.push(cpptools); + + await changeCppProperties({ + configurations: [ { name: "modules-provider", configurationProvider: provider.extensionId } ], + version: 4 + }, + disposables); + }); + + suiteTeardown(async function(): Promise { + disposables.forEach(d => d.dispose()); + await util.deleteFile(cppPropertiesPath()); + await util.deleteFile(compileCommandsPath()); + }); + + test("GCC-style module compiler args are delivered - main2.cpp", async () => { + const document: vscode.TextDocument = await openAndAwaitIntelliSenseReady(cpptools, disposables, "main2.cpp"); + const expected: api.SourceFileConfigurationItem[] = [ { uri: document.uri.toString(), configuration: gccModuleConfig } ]; + assert.strictEqual(configurationProvidedBeforeCanProvide, false); + assert.deepStrictEqual(lastResult, expected); + }); + + test("MSVC-style module compiler args are delivered - main3.cpp", async () => { + const document: vscode.TextDocument = await openAndAwaitIntelliSenseReady(cpptools, disposables, "main3.cpp"); + const expected: api.SourceFileConfigurationItem[] = [ { uri: document.uri.toString(), configuration: msvcModuleConfig } ]; + assert.strictEqual(configurationProvidedBeforeCanProvide, false); + assert.deepStrictEqual(lastResult, expected); + }); +}); From 5c6218835a9a126fd04b0929922e686c53fea053 Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Fri, 16 Oct 2026 20:19:36 +0330 Subject: [PATCH 2/4] Fix path handling and disposal in CppModulesProject tests --- .../tests/modules.integration.test.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts b/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts index bae8a4745..7baecd24e 100644 --- a/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts +++ b/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts @@ -24,6 +24,7 @@ import * as assert from 'assert'; import { suite } from 'mocha'; +import * as path from 'path'; import * as vscode from 'vscode'; import * as api from 'vscode-cpptools'; import * as apit from 'vscode-cpptools/out/testApi'; @@ -51,12 +52,15 @@ function fakeGccPath(): string { return toPosixPath(`${workspacePath()}/bin/${process.platform === 'win32' ? 'gcc.exe' : 'gcc'}`); } +// The directories of these files are created on demand by util.writeFileText, +// but only when the path uses native separators, hence path.join here. The +// contents written to the JSON files use posix paths (see toPosixPath). function compileCommandsPath(): string { - return `${workspacePath()}/build/compile_commands.json`; + return path.join(workspacePath(), 'build', 'compile_commands.json'); } function cppPropertiesPath(): string { - return workspacePath() + "/.vscode/c_cpp_properties.json"; + return path.join(workspacePath(), '.vscode', 'c_cpp_properties.json'); } async function changeCppProperties(cppProperties: config.ConfigurationJson, _disposables: vscode.Disposable[]): Promise { @@ -69,7 +73,7 @@ async function changeCppProperties(cppProperties: config.ConfigurationJson, _dis } async function openAndAwaitIntelliSenseReady(cpptools: apit.CppToolsTestApi, disposables: vscode.Disposable[], filename: string): Promise { - const path: string = workspacePath() + "/" + filename; + const filePath: string = workspacePath() + "/" + filename; const testHook: apit.CppToolsTestHook = cpptools.getTestHook(); const testResult: Promise = new Promise((resolve, reject) => { @@ -81,9 +85,8 @@ async function openAndAwaitIntelliSenseReady(cpptools: apit.CppToolsTestApi, dis })); setTimeout(() => { reject(new Error(`timeout waiting for IntelliSenseReady: ${filename}`)); }, testHelpers.defaultTimeout); }); - disposables.push(testHook); - const document: vscode.TextDocument = await vscode.workspace.openTextDocument(path); + const document: vscode.TextDocument = await vscode.workspace.openTextDocument(filePath); await vscode.window.showTextDocument(document); await testResult; return document; @@ -255,13 +258,13 @@ suite("custom configuration provider with module compiler args", function(): voi return Promise.resolve(false); }, provideBrowseConfiguration(): Thenable { - return Promise.reject(new Error("no browse configuration")); + return Promise.resolve({ browsePath: [] }); }, canProvideBrowseConfigurationsPerFolder(): Thenable { return Promise.resolve(false); }, provideFolderBrowseConfiguration(_uri: vscode.Uri): Thenable { - return Promise.reject(new Error("no folder browse configuration")); + return Promise.resolve({ browsePath: [] }); } }; From e6d54fe824596ec3b42b32842bb0740bfb8cae13 Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Sat, 17 Oct 2026 09:33:57 +0330 Subject: [PATCH 3/4] Verify delivered configuration via sentinel defines and hover assertions --- .../CppModulesProject/assets/main.cpp | 14 ++++ .../CppModulesProject/assets/main2.cpp | 13 ++++ .../CppModulesProject/assets/main3.cpp | 13 ++++ .../tests/modules.integration.test.ts | 66 +++++++++++++++++-- 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/Extension/test/scenarios/CppModulesProject/assets/main.cpp b/Extension/test/scenarios/CppModulesProject/assets/main.cpp index 58d63e77f..4f3b8a6c3 100644 --- a/Extension/test/scenarios/CppModulesProject/assets/main.cpp +++ b/Extension/test/scenarios/CppModulesProject/assets/main.cpp @@ -7,6 +7,20 @@ import math; import geometry; +// Sentinel probe: this define is only provided by the compile_commands.json +// entry for this file. The test hovers on the use of the symbol below to +// verify that the compilation database entry -- including its module related +// flags -- was actually applied to this translation unit, rather than +// IntelliSense falling back to a default configuration. +#ifdef CPPMODULES_COMPILE_COMMANDS_SENTINEL +int compile_database_sentinel_value = 1; +#endif + +int probe_compile_database() +{ + return compile_database_sentinel_value; +} + int main() { return add(1, 2) + square_area(3) - math::k_answer; diff --git a/Extension/test/scenarios/CppModulesProject/assets/main2.cpp b/Extension/test/scenarios/CppModulesProject/assets/main2.cpp index 9e0f2347c..950841de5 100644 --- a/Extension/test/scenarios/CppModulesProject/assets/main2.cpp +++ b/Extension/test/scenarios/CppModulesProject/assets/main2.cpp @@ -4,6 +4,19 @@ import math; import geometry; +// Sentinel probe: this define is only provided through the compilerArgs of the +// custom configuration provider used by the tests. The hover assertion proves +// that the provider's arguments (which also carry the GCC module flags) were +// delivered to the language server for this translation unit. +#ifdef PROVIDER_GCC_SENTINEL +int provider_gcc_sentinel_value = 1; +#endif + +int probe_provider_gcc() +{ + return provider_gcc_sentinel_value; +} + int main() { return add(2, 3) + square_area(4) - math::k_answer; diff --git a/Extension/test/scenarios/CppModulesProject/assets/main3.cpp b/Extension/test/scenarios/CppModulesProject/assets/main3.cpp index 743a47db5..ca0de74f1 100644 --- a/Extension/test/scenarios/CppModulesProject/assets/main3.cpp +++ b/Extension/test/scenarios/CppModulesProject/assets/main3.cpp @@ -4,6 +4,19 @@ import math; import geometry; +// Sentinel probe: this define is only provided through the compilerArgs of the +// custom configuration provider used by the tests. The hover assertion proves +// that the provider's arguments (which also carry the MSVC module arguments) +// were delivered to the language server for this translation unit. +#ifdef PROVIDER_MSVC_SENTINEL +int provider_msvc_sentinel_value = 1; +#endif + +int probe_provider_msvc() +{ + return provider_msvc_sentinel_value; +} + int main() { return add(3, 4) + square_area(5) - math::k_answer; diff --git a/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts b/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts index 7baecd24e..4c34520b2 100644 --- a/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts +++ b/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts @@ -14,8 +14,11 @@ // * module interface files (.cppm/.ixx) are recognized as C++ sources, // * a compile_commands.json that contains GCC module related flags does not // prevent the language server from providing IntelliSense for the project, +// and its entries are actually applied to the translation units (verified +// through a sentinel define and a hover assertion, not just readiness), // * module related compiler arguments (both GCC and MSVC spellings) are -// accepted and delivered through the custom configuration provider path. +// accepted and delivered through the custom configuration provider path, +// verified the same way. // // No statement is made about the semantic understanding of module imports by // the language server itself: that support is still in development and is @@ -92,6 +95,33 @@ async function openAndAwaitIntelliSenseReady(cpptools: apit.CppToolsTestApi, dis return document; } +// Verifies, through IntelliSense, that a sentinel define reached the language +// server for the given document: hovering over a use of a symbol that is only +// declared under "#ifdef " resolves if and only if the define was +// applied to the translation unit. This observes the *effect* of the delivered +// configuration instead of merely re-checking what a provider returned. +async function assertSentinelDefineApplied(document: vscode.TextDocument, useStatement: string, symbol: string): Promise { + const text: string = document.getText(); + const useIndex: number = text.indexOf(useStatement); + assert.ok(useIndex >= 0, `use statement not found in document: ${useStatement}`); + const symbolOffsetInUse: number = useStatement.indexOf(symbol); + assert.ok(symbolOffsetInUse >= 0, `symbol not found in use statement: ${symbol}`); + + // +1 to hover from inside the token. + const position: vscode.Position = document.positionAt(useIndex + symbolOffsetInUse + 1); + + // The hover can lag slightly behind IntelliSenseReady; retry for a while. + const deadline: number = Date.now() + testHelpers.defaultTimeout; + while (Date.now() < deadline) { + const hovers: vscode.Hover[] = await vscode.commands.executeCommand('vscode.executeHoverProvider', document.uri, position); + if (hovers.length > 0) { + return; + } + await testHelpers.delay(500); + } + assert.fail(`hover did not resolve '${symbol}' -- the sentinel define was not applied to ${document.uri.fsPath}`); +} + /* **************************************************************************** */ interface CompileCommandEntry { @@ -111,12 +141,12 @@ function gccModuleArgs(cmiDir: string): string[] { ]; } -function compileCommandEntry(file: string): CompileCommandEntry { +function compileCommandEntry(file: string, extraArgs: string[] = []): CompileCommandEntry { const root: string = toPosixPath(workspacePath()); const objectFile: string = `${root}/build/${file.replace(/\.[^.]+$/, "")}.o`; return { directory: root, - arguments: [fakeGccPath(), ...gccModuleArgs(`${root}/build/cmi`), "-c", "-o", objectFile, file], + arguments: [fakeGccPath(), ...gccModuleArgs(`${root}/build/cmi`), ...extraArgs, "-c", "-o", objectFile, file], file: file }; } @@ -175,7 +205,11 @@ suite("compile_commands.json with GCC C++20 module flags", function(): void { disposables.push(cpptools); await writeCompileCommands([ - compileCommandEntry("main.cpp"), + // The sentinel define carried by main.cpp's entry lets the test + // verify through IntelliSense (hover) that the database entry was + // applied, rather than IntelliSense falling back to a default + // configuration. + compileCommandEntry("main.cpp", ["-DCPPMODULES_COMPILE_COMMANDS_SENTINEL=1"]), compileCommandEntry("math.cppm"), compileCommandEntry("math.ixx") ]); @@ -199,7 +233,12 @@ suite("compile_commands.json with GCC C++20 module flags", function(): void { }); test("IntelliSense is provided for the module importer (main.cpp)", async () => { - await openAndAwaitIntelliSenseReady(cpptools, disposables, "main.cpp"); + const document: vscode.TextDocument = await openAndAwaitIntelliSenseReady(cpptools, disposables, "main.cpp"); + + // Verify that the compile_commands.json entry for this file -- the one + // carrying the module related flags -- was actually applied to the + // translation unit (and not a fallback configuration). + await assertSentinelDefineApplied(document, "return compile_database_sentinel_value;", "compile_database_sentinel_value"); }); }); @@ -213,16 +252,21 @@ suite("custom configuration provider with module compiler args", function(): voi const disposables: vscode.Disposable[] = []; // gcc-style module args for main2.cpp, MSVC-style module args for main3.cpp. + // Each config also carries a sentinel define -- through the provider API's + // `defines` channel, which is the documented way to deliver defines -- so + // the tests can verify through IntelliSense (hover) that the configuration + // was actually delivered and applied for the translation unit, not merely + // returned by the provider. The module flags stay in `compilerArgs`. const gccModuleConfig: api.SourceFileConfiguration = { includePath: [ "${workspaceFolder}" ], - defines: [], + defines: [ "PROVIDER_GCC_SENTINEL=1" ], intelliSenseMode: "gcc-x64", standard: "c++20", compilerArgs: gccModuleArgs(`${toPosixPath(workspacePath())}/build/cmi`) }; const msvcModuleConfig: api.SourceFileConfiguration = { includePath: [ "${workspaceFolder}" ], - defines: [], + defines: [ "PROVIDER_MSVC_SENTINEL=1" ], intelliSenseMode: "msvc-x64", standard: "c++20", compilerArgs: msvcModuleArgs() @@ -292,6 +336,10 @@ suite("custom configuration provider with module compiler args", function(): voi const expected: api.SourceFileConfigurationItem[] = [ { uri: document.uri.toString(), configuration: gccModuleConfig } ]; assert.strictEqual(configurationProvidedBeforeCanProvide, false); assert.deepStrictEqual(lastResult, expected); + + // Verify that the compilerArgs (including the GCC module flags) were + // delivered to the language server for this translation unit. + await assertSentinelDefineApplied(document, "return provider_gcc_sentinel_value;", "provider_gcc_sentinel_value"); }); test("MSVC-style module compiler args are delivered - main3.cpp", async () => { @@ -299,5 +347,9 @@ suite("custom configuration provider with module compiler args", function(): voi const expected: api.SourceFileConfigurationItem[] = [ { uri: document.uri.toString(), configuration: msvcModuleConfig } ]; assert.strictEqual(configurationProvidedBeforeCanProvide, false); assert.deepStrictEqual(lastResult, expected); + + // Verify that the compilerArgs (including the MSVC module arguments) + // were delivered to the language server for this translation unit. + await assertSentinelDefineApplied(document, "return provider_msvc_sentinel_value;", "provider_msvc_sentinel_value"); }); }); From 6e9be5830b678e8a27b9eb3444840ff0072b6ebf Mon Sep 17 00:00:00 2001 From: soroshsabz Date: Sat, 17 Oct 2026 16:47:00 +0330 Subject: [PATCH 4/4] Remove tautological provider state comparisons --- .../tests/modules.integration.test.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts b/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts index 4c34520b2..8702cefe7 100644 --- a/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts +++ b/Extension/test/scenarios/CppModulesProject/tests/modules.integration.test.ts @@ -333,23 +333,29 @@ suite("custom configuration provider with module compiler args", function(): voi test("GCC-style module compiler args are delivered - main2.cpp", async () => { const document: vscode.TextDocument = await openAndAwaitIntelliSenseReady(cpptools, disposables, "main2.cpp"); - const expected: api.SourceFileConfigurationItem[] = [ { uri: document.uri.toString(), configuration: gccModuleConfig } ]; + // Handshake check: the extension asked before configurations were + // provided, and items were delivered for this file. (The objects + // themselves are intentionally not re-compared against the provider's + // own return value; the assertions below observe the delivered + // configuration through its effect on IntelliSense instead.) assert.strictEqual(configurationProvidedBeforeCanProvide, false); - assert.deepStrictEqual(lastResult, expected); + assert.deepStrictEqual(lastResult.map(item => item.uri), [document.uri.toString()]); - // Verify that the compilerArgs (including the GCC module flags) were - // delivered to the language server for this translation unit. + // Verify that the configuration -- whose compilerArgs carry the GCC + // module flags -- was actually delivered and applied for this + // translation unit. await assertSentinelDefineApplied(document, "return provider_gcc_sentinel_value;", "provider_gcc_sentinel_value"); }); test("MSVC-style module compiler args are delivered - main3.cpp", async () => { const document: vscode.TextDocument = await openAndAwaitIntelliSenseReady(cpptools, disposables, "main3.cpp"); - const expected: api.SourceFileConfigurationItem[] = [ { uri: document.uri.toString(), configuration: msvcModuleConfig } ]; + // Handshake check, as above. assert.strictEqual(configurationProvidedBeforeCanProvide, false); - assert.deepStrictEqual(lastResult, expected); + assert.deepStrictEqual(lastResult.map(item => item.uri), [document.uri.toString()]); - // Verify that the compilerArgs (including the MSVC module arguments) - // were delivered to the language server for this translation unit. + // Verify that the configuration -- whose compilerArgs carry the MSVC + // module arguments -- was actually delivered and applied for this + // translation unit. await assertSentinelDefineApplied(document, "return provider_msvc_sentinel_value;", "provider_msvc_sentinel_value"); }); });