11import assert from "node:assert/strict" ;
2+ import { mkdtempSync , mkdirSync , rmSync , writeFileSync } from "node:fs" ;
3+ import os from "node:os" ;
4+ import path from "node:path" ;
25import test from "node:test" ;
36
47import {
@@ -7,8 +10,12 @@ import {
710 xlingsInstallArgs ,
811 deriveInstalledClangdPath ,
912 findXlingsExecutable ,
13+ resolveXlingsExecutable ,
1014} from "../src/llvmTools" ;
1115
16+ // `xlings` on POSIX, `xlings.exe` on Windows — mirrors mcpp's exe_suffix.
17+ const xlingsBinaryName = process . platform === "win32" ? "xlings.exe" : "xlings" ;
18+
1219test ( "extracts version string from ToolIdentity" , ( ) => {
1320 assert . equal (
1421 llvmToolsVersionSpec ( { major : 22 , minor : 1 , patch : 8 , revision : "abc1234" } ) ,
@@ -68,3 +75,115 @@ test("findXlingsExecutable returns a string or undefined", () => {
6875 // Returns string (PATH fallback or known path) or undefined if xlings not found
6976 assert . ok ( result === undefined || typeof result === "string" ) ;
7077} ) ;
78+
79+ test ( "findXlingsExecutable finds the xlings bundled in $MCPP_HOME/registry/bin" , ( ) => {
80+ const home = mkdtempSync ( path . join ( os . tmpdir ( ) , "mcpp-vscode-mcpp-home-" ) ) ;
81+ const registryBin = path . join ( home , "registry" , "bin" ) ;
82+ const xlingsPath = path . join ( registryBin , xlingsBinaryName ) ;
83+ mkdirSync ( registryBin , { recursive : true } ) ;
84+ writeFileSync ( xlingsPath , "#!/bin/sh\n" ) ;
85+ try {
86+ assert . equal (
87+ findXlingsExecutable ( { home, env : { MCPP_HOME : home } } ) ,
88+ xlingsPath ,
89+ ) ;
90+ } finally {
91+ rmSync ( home , { recursive : true , force : true } ) ;
92+ }
93+ } ) ;
94+
95+ test ( "findXlingsExecutable falls back to $HOME/.mcpp/registry/bin when MCPP_HOME is unset" , ( ) => {
96+ const home = mkdtempSync ( path . join ( os . tmpdir ( ) , "mcpp-vscode-home-" ) ) ;
97+ const registryBin = path . join ( home , ".mcpp" , "registry" , "bin" ) ;
98+ const xlingsPath = path . join ( registryBin , xlingsBinaryName ) ;
99+ mkdirSync ( registryBin , { recursive : true } ) ;
100+ writeFileSync ( xlingsPath , "#!/bin/sh\n" ) ;
101+ try {
102+ assert . equal (
103+ findXlingsExecutable ( { home, env : { } } ) ,
104+ xlingsPath ,
105+ ) ;
106+ } finally {
107+ rmSync ( home , { recursive : true , force : true } ) ;
108+ }
109+ } ) ;
110+
111+ test ( "findXlingsExecutable honors MCPP_VENDORED_XLINGS" , ( ) => {
112+ const root = mkdtempSync ( path . join ( os . tmpdir ( ) , "mcpp-vscode-vendored-" ) ) ;
113+ const vendored = path . join ( root , "opt-mcpp" , "registry" , "bin" , xlingsBinaryName ) ;
114+ mkdirSync ( path . dirname ( vendored ) , { recursive : true } ) ;
115+ writeFileSync ( vendored , "#!/bin/sh\n" ) ;
116+ try {
117+ assert . equal (
118+ findXlingsExecutable ( { home : root , env : { MCPP_VENDORED_XLINGS : vendored } } ) ,
119+ vendored ,
120+ ) ;
121+ } finally {
122+ rmSync ( root , { recursive : true , force : true } ) ;
123+ }
124+ } ) ;
125+
126+ test ( "resolveXlingsExecutable reads the xlings binary from `mcpp self env`" , async ( ) => {
127+ const root = mkdtempSync ( path . join ( os . tmpdir ( ) , "mcpp-vscode-selfenv-" ) ) ;
128+ const xlingsPath = path . join ( root , "registry" , "bin" , xlingsBinaryName ) ;
129+ mkdirSync ( path . dirname ( xlingsPath ) , { recursive : true } ) ;
130+ writeFileSync ( xlingsPath , "#!/bin/sh\n" ) ;
131+ const runner = async ( ) => ( {
132+ exitCode : 0 ,
133+ stdout : `MCPP_HOME = ${ root } \nxlings binary = ${ xlingsPath } \nxlings pinned = 2026.8.8.1\n` ,
134+ stderr : "" ,
135+ } ) ;
136+ try {
137+ assert . equal (
138+ await resolveXlingsExecutable ( "/tools/mcpp" , runner ) ,
139+ xlingsPath ,
140+ ) ;
141+ } finally {
142+ rmSync ( root , { recursive : true , force : true } ) ;
143+ }
144+ } ) ;
145+
146+ test ( "resolveXlingsExecutable passes a timeout to `mcpp self env`" , async ( ) => {
147+ let captured : { timeoutMs ?: number } | undefined ;
148+ const runner = async (
149+ _executable : string ,
150+ _args : string [ ] ,
151+ _cwd ?: string ,
152+ options ?: { timeoutMs ?: number } ,
153+ ) => {
154+ captured = options ;
155+ return { exitCode : 0 , stdout : "" , stderr : "" } ;
156+ } ;
157+ await resolveXlingsExecutable ( "/tools/mcpp" , runner ) ;
158+ assert . equal ( captured ?. timeoutMs , 60_000 ) ;
159+ } ) ;
160+
161+ test ( "resolveXlingsExecutable falls back to path probing when the reported path does not exist" , async ( ) => {
162+ const home = mkdtempSync ( path . join ( os . tmpdir ( ) , "mcpp-vscode-fallback-missing-" ) ) ;
163+ const runner = async ( ) => ( {
164+ exitCode : 0 ,
165+ stdout : "xlings binary = /no/such/xlings\n" ,
166+ stderr : "" ,
167+ } ) ;
168+ try {
169+ assert . equal (
170+ await resolveXlingsExecutable ( "/tools/mcpp" , runner , { home, env : { } } ) ,
171+ undefined ,
172+ ) ;
173+ } finally {
174+ rmSync ( home , { recursive : true , force : true } ) ;
175+ }
176+ } ) ;
177+
178+ test ( "resolveXlingsExecutable falls back to path probing when `mcpp self env` fails" , async ( ) => {
179+ const home = mkdtempSync ( path . join ( os . tmpdir ( ) , "mcpp-vscode-fallback-fail-" ) ) ;
180+ const runner = async ( ) => ( { exitCode : 1 , stdout : "" , stderr : "boom\n" } ) ;
181+ try {
182+ assert . equal (
183+ await resolveXlingsExecutable ( "/tools/mcpp" , runner , { home, env : { } } ) ,
184+ undefined ,
185+ ) ;
186+ } finally {
187+ rmSync ( home , { recursive : true , force : true } ) ;
188+ }
189+ } ) ;
0 commit comments