deploy: current vibn theia state
Some checks failed
Playwright Tests / Playwright Tests (ubuntu-22.04, Node.js 22.x) (push) Has been cancelled
3PP License Check / 3PP License Check (11, 22.x, ubuntu-22.04) (push) Has been cancelled
Publish packages to NPM / Perform Publishing (push) Has been cancelled

Made-with: Cursor
This commit is contained in:
2026-02-27 12:01:08 -08:00
commit 8bb5110148
3782 changed files with 640947 additions and 0 deletions

View File

@@ -0,0 +1,486 @@
// *****************************************************************************
// Copyright (C) 2020 Ericsson and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/**
* This test suite assumes that we run in a NodeJS environment!
*/
import { spawn, execSync, SpawnOptions, ChildProcess, spawnSync } from 'child_process';
import { Readable } from 'stream';
import { join } from 'path';
import { ShellCommandBuilder, CommandLineOptions, ProcessInfo } from './shell-command-builder';
import * as chalk from 'chalk'; // tslint:disable-line:no-implicit-dependencies
export interface TestProcessInfo extends ProcessInfo {
shell: ChildProcess
}
const isWindows = process.platform === 'win32';
/**
* Extra debugging info (very verbose).
*/
const _debug: boolean = Boolean(process.env['THEIA_PROCESS_TEST_DEBUG']);
/**
* On Windows, some shells simply mess up the terminal's output.
* Enable if you still want to test those.
*/
const _runWeirdShell: true | undefined = Boolean(process.env['THEIA_PROCESS_TEST_WEIRD_SHELL']) || undefined;
/**
* You might only have issues with a specific shell (`cmd.exe` I am looking at you).
*/
const _onlyTestShell: string | undefined = process.env['THEIA_PROCESS_TEST_ONLY'] || undefined;
/**
* Only log if environment variable is set.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function debug(...parts: any[]): void {
if (_debug) {
console.debug(...parts);
}
}
const testResources = join(__dirname, '../../src/common/tests');
const spawnOptions: SpawnOptions = {
// We do our own quoting, don't rely on the one done by NodeJS:
windowsVerbatimArguments: true,
stdio: ['pipe', 'pipe', 'pipe'],
};
// Formatting options, used with `scanLines` for debugging.
const stdoutFormat = (prefix: string) => (data: string) =>
`${chalk.bold(chalk.yellow(`${prefix} STDOUT:`))} ${chalk.bgYellow(chalk.black(data))}`;
const stderrFormat = (prefix: string) => (data: string) =>
`${chalk.bold(chalk.red(`${prefix} STDERR:`))} ${chalk.bgRed(chalk.white(data))}`;
// Default error scanner
const errorScanner = (handle: ScanLineHandle<void>) => {
if (
/^\s*\w+Error:/.test(handle.line) ||
/^\s*Cannot find /.test(handle.line)
) {
throw new Error(handle.text);
}
};
// Yarn mangles the PATH and creates some proxy script around node(.exe),
// which messes up our environment, failing the tests.
const hostNodePath =
process.env['npm_node_execpath'] ||
process.env['NODE'];
if (!hostNodePath) {
throw new Error('Could not determine the real node path.');
}
const shellCommandBuilder = new ShellCommandBuilder();
const shellConfigs = [{
name: 'bash',
path: isWindows
? _runWeirdShell && execShellCommand('where bash.exe')
: execShellCommand('command -v bash'),
nodePath:
isWindows && 'node' // Good enough
}, {
name: 'wsl',
path: isWindows
? _runWeirdShell && execShellCommand('where wsl.exe')
: undefined,
nodePath:
isWindows && 'node' // Good enough
}, {
name: 'cmd',
path: isWindows
? execShellCommand('where cmd.exe')
: undefined,
}, {
name: 'powershell',
path: execShellCommand(isWindows
? 'where powershell'
: 'command -v pwsh'),
}];
/* eslint-disable max-len */
// 18d/12m/19y - Ubuntu 16.04:
// Powershell sometimes fails when running as part of an npm lifecycle script.
// See following error:
//
//
// FailFast:
// The type initializer for 'Microsoft.PowerShell.ApplicationInsightsTelemetry' threw an exception.
//
// at System.Environment.FailFast(System.String, System.Exception)
// at System.Environment.FailFast(System.String, System.Exception)
// at Microsoft.PowerShell.UnmanagedPSEntry.Start(System.String, System.String[], Int32)
// at Microsoft.PowerShell.ManagedPSEntry.Main(System.String[])
//
// Exception details:
// System.TypeInitializationException: The type initializer for 'Microsoft.PowerShell.ApplicationInsightsTelemetry' threw an exception. ---> System.ArgumentException: Item has already been added. Key in dictionary: 'SPAWN_WRAP_SHIM_ROOT' Key being added: 'SPAWN_WRAP_SHIM_ROOT'
// at System.Collections.Hashtable.Insert(Object key, Object nvalue, Boolean add)
// at System.Environment.ToHashtable(IEnumerable`1 pairs)
// at System.Environment.GetEnvironmentVariables()
// at Microsoft.ApplicationInsights.Extensibility.Implementation.Platform.PlatformImplementation..ctor()
// at Microsoft.ApplicationInsights.Extensibility.Implementation.Platform.PlatformSingleton.get_Current()
// at Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryConfigurationFactory.Initialize(TelemetryConfiguration configuration, TelemetryModules modules)
// at Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration.get_Active()
// at Microsoft.PowerShell.ApplicationInsightsTelemetry..cctor()
// --- End of inner exception stack trace ---
// at Microsoft.PowerShell.ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry()
// at Microsoft.PowerShell.ConsoleHost.Start(String bannerText, String helpText, String[] args)
// at Microsoft.PowerShell.ConsoleShell.Start(String bannerText, String helpText, String[] args)
// at Microsoft.PowerShell.UnmanagedPSEntry.Start(String consoleFilePath, String[] args, Int32 argc)
/* eslint-enable max-len */
let id = 0;
for (const shellConfig of shellConfigs) {
let skipMessage: string | undefined;
if (typeof _onlyTestShell === 'string' && shellConfig.name !== _onlyTestShell) {
skipMessage = `only testing ${_onlyTestShell}`;
} else if (!shellConfig.path) {
// For each shell, skip if we could not find the executable path.
skipMessage = 'cannot find shell';
} else {
// Run a test in the shell to catch runtime issues.
// CI seems to have issues with some shells depending on the environment...
try {
const debugName = `${shellConfig.name}/test`;
const shellTest = spawnSync(shellConfig.path, {
input: 'echo abcdefghijkl\n\n',
timeout: 5_000,
});
debug(stdoutFormat(debugName)(shellTest.stdout.toString()));
debug(stderrFormat(debugName)(shellTest.stderr.toString()));
if (!/abcdefghijkl/m.test(shellTest.output.toString())) {
skipMessage = 'wrong test output';
}
} catch (error) {
console.error(error);
skipMessage = 'error occurred';
}
}
/**
* If skipMessage is set, we should skip the test and explain why.
*/
const describeOrSkip = (callback: (this: Mocha.Suite) => void) => {
const describeMessage = `test ${shellConfig.name} commands`;
if (typeof skipMessage === 'undefined') {
describe(describeMessage, callback);
} else {
describe.skip(`${describeMessage} - skip: ${skipMessage}`, callback);
}
};
describeOrSkip(function (): void {
this.timeout(10_000);
let nodePath: string;
let cwd: string;
let submit: string | undefined;
let processInfo: TestProcessInfo;
let context: TestCaseContext;
beforeEach(() => {
// In WSL, the node path is different than the host one (Windows vs Linux).
nodePath = shellConfig.nodePath || hostNodePath;
// On windows, when running bash we need to convert paths from Windows
// to their mounting point, assuming bash is running within WSL.
if (isWindows && /bash|wsl/.test(shellConfig.name)) {
cwd = convertWindowsPath(testResources);
} else {
cwd = testResources;
}
// When running powershell, it seems like good measure to send `\n` twice...
if (shellConfig.name === 'powershell') {
submit = '\n\n';
}
// TestContext holds all state for a given test.
const testContextName = `${shellConfig.name}/${++id}`;
context = new TestCaseContext(testContextName, submit);
processInfo = createShell(context, shellConfig.path!);
});
afterEach(() => {
processInfo.shell.kill();
context.finalize();
});
it('use simple environment variables', async () => {
const envName = 'SIMPLE_NAME';
const envValue = 'SIMPLE_VALUE';
await testCommandLine(
context, processInfo,
{
cwd, args: [nodePath, '-p', `\`[\${process.env['${envName}']}]\``],
env: {
[envName]: envValue,
}
}, [
// stderr
scanLines<void>(context, processInfo.shell.stderr!, errorScanner, stderrFormat(context.name)),
// stdout
scanLines<void>(context, processInfo.shell.stdout!, handle => {
errorScanner(handle);
if (handle.line.includes(`[${envValue}]`)) {
handle.resolve();
}
}, stdoutFormat(context.name)),
]);
});
it('use problematic environment variables', async () => {
const envName = 'A?B_C | D $PATH';
const envValue = 'SUCCESS';
await testCommandLine(
context, processInfo,
{
cwd, args: [nodePath, '-p', `\`[\${process.env['${envName}']}]\``],
env: {
[envName]: envValue,
}
}, [
// stderr
scanLines<void>(context, processInfo.shell.stderr!, errorScanner, stderrFormat(context.name)),
// stdout
scanLines<void>(context, processInfo.shell.stdout!, handle => {
errorScanner(handle);
if (handle.line.includes(`[${envValue}]`)) {
handle.resolve();
}
if (handle.line.includes('[undefined]')) {
handle.reject(new Error(handle.text));
}
}, stdoutFormat(context.name)),
]);
});
it('command with complex arguments', async () => {
const left = 'ABC';
const right = 'DEF';
await testCommandLine(
context, processInfo,
{
cwd, args: [nodePath, '-e', `{
const left = '${left}';
const right = '${right}';
console.log(\`[\${left}|\${right}]\`);
}`],
}, [
// stderr
scanLines<void>(context, processInfo.shell.stderr!, errorScanner, stderrFormat(context.name)),
// stdout
scanLines<void>(context, processInfo.shell.stdout!, handle => {
errorScanner(handle);
if (handle.line.includes(`[${left}|${right}]`)) {
handle.resolve();
}
}, stdoutFormat(context.name)),
]);
});
});
}
/**
* Allow `command` to fail and return undefined instead.
*/
function execShellCommand(command: string): string | undefined {
try {
// If trimmed output is an empty string, return `undefined` instead:
return execSync(command).toString().trim() || undefined;
} catch (error) {
console.error(command, error);
return undefined;
}
}
/**
* When executing `bash.exe` on Windows, the `C:`, `D:`, etc drives are mounted under `/mnt/<drive>/...`
*/
function convertWindowsPath(windowsPath: string): string {
return windowsPath
// Convert back-slashes to forward-slashes
.replace(/\\/g, '/')
// Convert drive-letter to usual mounting point in WSL
.replace(/^[A-Za-z]:\//, s => `/mnt/${s[0].toLowerCase()}/`);
}
/**
* Display trailing whitespace in a string, such as \r and \n.
*/
function displayWhitespaces(line: string): string {
return line
.replace(/\r?\n/, s => s.length === 2 ? '<\\r\\n>\r\n' : '<\\n>\n');
}
/**
* Actually run `prepareCommandLine`.
*/
async function testCommandLine(
context: TestCaseContext,
processInfo: TestProcessInfo,
options: CommandLineOptions,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
firstOf: Array<Promise<any>>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> {
const commandLine = shellCommandBuilder.buildCommand(processInfo, options);
debug(`${chalk.bold(chalk.white(`${context.name} STDIN:`))} ${chalk.bgWhite(chalk.black(displayWhitespaces(commandLine)))}`);
processInfo.shell.stdin!.write(commandLine + context.submit);
return Promise.race(firstOf);
}
/**
* Creates a `(Test)ProcessInfo` object by spawning the specified shell.
*/
function createShell(
context: TestCaseContext,
shellExecutable: string,
shellArguments: string[] = []
): TestProcessInfo {
const shell = spawn(shellExecutable, shellArguments, spawnOptions);
debug(chalk.magenta(`${chalk.bold(`${context.name} SPAWN:`)} ${shellExecutable} ${shellArguments.join(' ')}`));
shell.on('close', (code, signal) => debug(chalk.magenta(
`${chalk.bold(`${context.name} CLOSE:`)} ${shellExecutable} code(${code}) signal(${signal})`
)));
return {
executable: shellExecutable,
arguments: [],
shell,
};
}
/**
* Fire `callback` once per new detected line.
*/
async function scanLines<T = void>(
context: TestCaseContext,
stream: Readable,
callback: (handle: ScanLineHandle<T>) => void,
debugFormat = (s: string) => s,
): Promise<T> {
return new Promise((resolve, reject) => {
let line = '';
let text = '';
stream.on('close', () => {
debug(debugFormat('<CLOSED>'));
});
// The `data` listener will be collected on 'close', which will happen
// once we kill the process.
stream.on('data', data => {
if (context.resolved) {
return;
}
const split = data.toString().split('\n');
while (!context.resolved && split.length > 1) {
line += split.shift()! + '\n';
text += line;
debug(debugFormat(displayWhitespaces(line)));
try {
callback({
resolve: (value: T) => {
if (!context.resolved) {
context.resolve();
resolve(value);
debug(chalk.bold(chalk.green(`${context.name} SCANLINES RESOLVED`)));
}
},
reject: (reason?: Error) => {
if (!context.resolved) {
context.resolve();
reject(reason);
debug(chalk.bold(chalk.red(`${context.name} SCANLINES REJECTED`)));
}
},
line,
text,
});
} catch (error) {
debug(chalk.bold(chalk.red(`${context.name} SCANLINES THROWED`)));
context.resolve();
reject(error);
break;
}
line = '';
}
line += split[0];
});
});
}
interface ScanLineHandle<T> {
/**
* Finish listening to new events with a return value.
*/
resolve: (value: T) => void
/**
* Finish listening to new events with an error.
*/
reject: (reason?: Error) => void
/**
* Currently parsed line.
*/
line: string
/**
* The whole output buffer, containing all lines.
*/
text: string
}
/**
* We need a test case context to help with catching listeners that timed-out,
* and synchronize multiple listeners so that when one resolves the test case,
* the others can be put in "sleep mode" until destruction.
*/
class TestCaseContext {
constructor(
/**
* A name associated with this context, to help with debugging.
*/
readonly name: string,
/**
* The characters to send in order to submit a command (mostly
* powershell is causing issues).
*/
public submit = '\n',
/**
* @internal Current state of the test case, if it is finished or not.
*/
public resolved = false
) { }
resolve(): void {
this.resolved = true;
}
finalize(): void {
if (!this.resolved) {
this.resolve();
debug(chalk.red(`${chalk.bold(`${this.name} CONTEXT:`)} context wasn't resolved when finalizing, resolving!`));
}
}
}