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
24 changes: 24 additions & 0 deletions test/sanity/scripts/run-win32.cmd
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
@echo off
setlocal

set "UBUNTU_ROOTFS=%TEMP%\ubuntu-rootfs.tar.gz"
set "UBUNTU_INSTALL=%LOCALAPPDATA%\WSL\Ubuntu"

echo Checking if Ubuntu WSL is available
powershell -Command "wsl -d Ubuntu echo 'WSL is ready'" 2>nul
if errorlevel 1 call :install_wsl

set PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
set PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe

echo Running sanity tests
node "%~dp0..\out\index.js" %*
goto :eof

:install_wsl
echo Ubuntu not found, installing via rootfs import

if not exist "%UBUNTU_ROOTFS%" (
echo Downloading Ubuntu rootfs
curl -L -o "%UBUNTU_ROOTFS%" https://cloud-images.ubuntu.com/wsl/jammy/current/ubuntu-jammy-wsl-amd64-ubuntu22.04lts.rootfs.tar.gz
)

echo Importing Ubuntu into WSL
mkdir "%UBUNTU_INSTALL%" 2>nul
wsl --import Ubuntu "%UBUNTU_INSTALL%" "%UBUNTU_ROOTFS%"

echo Starting WSL
wsl -d Ubuntu echo WSL is ready
goto :eof
93 changes: 90 additions & 3 deletions test/sanity/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { test } from 'mocha';
import fetch, { Response } from 'node-fetch';
import os from 'os';
import path from 'path';
import { Browser, chromium, webkit } from 'playwright';
import { Browser, chromium, Page, webkit } from 'playwright';
import { Capability, detectCapabilities } from './detectors.js';

/**
Expand All @@ -35,6 +35,7 @@ export class TestContext {
private static readonly codesignExclude = /node_modules\/(@parcel\/watcher\/build\/Release\/watcher\.node|@vscode\/deviceid\/build\/Release\/windows\.node|@vscode\/ripgrep\/bin\/rg|@vscode\/spdlog\/build\/Release\/spdlog.node|kerberos\/build\/Release\/kerberos.node|@vscode\/native-watchdog\/build\/Release\/watchdog\.node|node-pty\/build\/Release\/(pty\.node|spawn-helper)|vsda\/build\/Release\/vsda\.node|native-watchdog\/build\/Release\/watchdog\.node)$/;

private readonly tempDirs = new Set<string>();
private readonly wslTempDirs = new Set<string>();
private nextPort = 3010;

public constructor(public readonly options: Readonly<{
Expand Down Expand Up @@ -151,6 +152,51 @@ export class TestContext {
return tempDir;
}

/**
* Creates a new temporary directory in WSL and returns its path.
*/
public createWslTempDir(): string {
const tempDir = `/tmp/vscode-sanity-${Date.now()}-${Math.random().toString(36).slice(2)}`;
this.log(`Creating WSL temp directory: ${tempDir}`);
this.runNoErrors('wsl', 'mkdir', '-p', tempDir);
this.wslTempDirs.add(tempDir);
return tempDir;
}

/**
* Deletes a directory in WSL.
* @param dir The WSL directory path to delete.
*/
public deleteWslDir(dir: string): void {
this.log(`Deleting WSL directory: ${dir}`);
this.run('wsl', 'rm', '-rf', dir);
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using context.run without checking if the command succeeded. If the WSL rm command fails during cleanup, the error will be silently ignored. While this might be acceptable for cleanup operations in a try-catch block (as seen at line 227), it would be better to at least log the stderr output if the command fails. Consider adding error checking: if (result.status !== 0) { this.log(\Failed to delete WSL dir: ${result.stderr}`); }`

Suggested change
this.run('wsl', 'rm', '-rf', dir);
const result = this.run('wsl', 'rm', '-rf', dir);
if (result.status !== 0) {
this.log(`Failed to delete WSL directory ${dir}: ${result.stderr}`);
}

Copilot uses AI. Check for mistakes.
}

/**
* Converts a Windows path to a WSL path.
* @param windowsPath The Windows path to convert (e.g., 'C:\Users\test').
* @returns The WSL path (e.g., '/mnt/c/Users/test').
*/
public toWslPath(windowsPath: string): string {
return windowsPath
.replace(/^([A-Za-z]):/, (_, drive) => `/mnt/${drive.toLowerCase()}`)
.replaceAll('\\', '/');
}

/**
* Returns the name of the default WSL distribution.
* @returns The default WSL distribution name (e.g., 'Ubuntu').
*/
public getDefaultWslDistro(): string {
const result = this.runNoErrors('wsl', '--list', '--quiet');
const distro = result.stdout.trim().split('\n')[0].replace(/\0/g, '').trim();
if (!distro) {
this.error('No WSL distribution found');
}
this.log(`Default WSL distribution: ${distro}`);
return distro;
}

/**
* Ensures that the directory for the specified file path exists.
*/
Expand All @@ -175,6 +221,15 @@ export class TestContext {
}
}
this.tempDirs.clear();

for (const dir of this.wslTempDirs) {
try {
this.deleteWslDir(dir);
} catch (error) {
this.log(`Failed to delete WSL temp directory: ${dir}: ${error}`);
}
}
this.wslTempDirs.clear();
}

/**
Expand Down Expand Up @@ -773,9 +828,10 @@ export class TestContext {
/**
* Returns the entry point executable for the VS Code server in the specified directory.
* @param dir The directory containing unpacked server files.
* @param forWsl If true, returns the Linux entry point (for running in WSL on Windows).
* @returns The path to the server entry point executable.
*/
public getServerEntryPoint(dir: string): string {
public getServerEntryPoint(dir: string, forWsl = false): string {
let filename: string;
switch (this.options.quality) {
case 'stable':
Expand All @@ -789,7 +845,7 @@ export class TestContext {
break;
}

if (os.platform() === 'win32') {
if (os.platform() === 'win32' && !forWsl) {
filename += '.cmd';
}

Expand Down Expand Up @@ -863,6 +919,17 @@ export class TestContext {
}
}

/**
* Awaits a page promise and sets the default timeout.
* @param pagePromise The promise that resolves to a Page.
* @returns The page with the timeout configured.
*/
public async getPage(pagePromise: Promise<Page>): Promise<Page> {
const page = await pagePromise;
page.setDefaultTimeout(3 * 60 * 1000);
return page;
}

/**
* Constructs a web server URL with optional token and folder parameters.
* @param port The port number of the web server.
Expand Down Expand Up @@ -898,4 +965,24 @@ export class TestContext {
public getUniquePort(): string {
return String(this.nextPort++);
}

/**
* Returns the default WSL server extensions directory path.
* @returns The path to the extensions directory (e.g., '~/.vscode-server-insiders/extensions').
*/
public getWslServerExtensionsDir(): string {
let serverDir: string;
switch (this.options.quality) {
case 'stable':
serverDir = '.vscode-server';
break;
case 'insider':
serverDir = '.vscode-server-insiders';
break;
case 'exploration':
serverDir = '.vscode-server-exploration';
break;
}
return `~/${serverDir}/extensions`;
}
}
3 changes: 1 addition & 2 deletions test/sanity/src/desktop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,7 @@ export function setup(context: TestContext) {

context.log(`Starting VS Code ${entryPoint} with args ${args.join(' ')}`);
const app = await _electron.launch({ executablePath: entryPoint, args });
const window = await app.firstWindow();
window.setDefaultTimeout(2 * 60 * 1000);
const window = await context.getPage(app.firstWindow());

await test.run(window);

Expand Down
20 changes: 19 additions & 1 deletion test/sanity/src/detectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export type Capability =
| 'x64' | 'arm64' | 'arm32'
| 'deb' | 'rpm' | 'snap'
| 'desktop'
| 'browser';
| 'browser'
| 'wsl';

/**
* Detect the capabilities of the current environment.
Expand All @@ -26,6 +27,7 @@ export function detectCapabilities(): ReadonlySet<Capability> {
detectPackageManagers(capabilities);
detectDesktop(capabilities);
detectBrowser(capabilities);
detectWSL(capabilities);
return capabilities;
}

Expand Down Expand Up @@ -125,3 +127,19 @@ function detectBrowser(capabilities: Set<Capability>) {
}
}
}

/**
* Detect if WSL is available on Windows.
*/
function detectWSL(capabilities: Set<Capability>) {
if (process.platform !== 'win32') {
return;
}
const systemRoot = process.env['SystemRoot'];
if (systemRoot) {
const wslPath = `${systemRoot}\\System32\\wsl.exe`;
if (fs.existsSync(wslPath)) {
capabilities.add('wsl');
}
}
}
2 changes: 2 additions & 0 deletions test/sanity/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TestContext } from './context.js';
import { setup as setupDesktopTests } from './desktop.test.js';
import { setup as setupServerTests } from './server.test.js';
import { setup as setupServerWebTests } from './serverWeb.test.js';
import { setup as setupWSLTests } from './wsl.test.js';

const options = minimist(process.argv.slice(2), {
string: ['commit', 'quality'],
Expand Down Expand Up @@ -49,3 +50,4 @@ setupCliTests(context);
setupDesktopTests(context);
setupServerTests(context);
setupServerWebTests(context);
setupWSLTests(context);
5 changes: 1 addition & 4 deletions test/sanity/src/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,7 @@ export function setup(context: TestContext) {
}

async function runWebTest(url: string) {
context.log(`Fetching ${url}`);
const response = await fetch(url);
assert.strictEqual(response.status, 200, `Expected status 200 but got ${response.status}`);

const response = await context.fetchNoErrors(url);
const text = await response.text();
assert.strictEqual(text, context.options.commit, `Expected commit ${context.options.commit} but got ${text}`);
}
Expand Down
3 changes: 1 addition & 2 deletions test/sanity/src/serverWeb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,7 @@ export function setup(context: TestContext) {

async function runUITest(url: string, test: UITest) {
const browser = await context.launchBrowser();
const page = await browser.newPage();
page.setDefaultTimeout(2 * 60 * 1000);
const page = await context.getPage(browser.newPage());

context.log(`Navigating to ${url}`);
await page.goto(url, { waitUntil: 'networkidle' });
Expand Down
6 changes: 3 additions & 3 deletions test/sanity/src/uiTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class UITest {
private _userDataDir: string | undefined;

constructor(
private readonly context: TestContext,
protected readonly context: TestContext,
dataDir?: string
) {
if (dataDir) {
Expand Down Expand Up @@ -110,7 +110,7 @@ export class UITest {
/**
* Verify that the text file was created with the expected content.
*/
private verifyTextFileCreated() {
protected verifyTextFileCreated() {
this.context.log('Verifying file contents');
const filePath = `${this.workspaceDir}/helloWorld.txt`;
const fileContents = fs.readFileSync(filePath, 'utf-8');
Expand Down Expand Up @@ -139,7 +139,7 @@ export class UITest {
/**
* Verify that the GitHub Pull Requests extension is installed.
*/
private verifyExtensionInstalled() {
protected verifyExtensionInstalled() {
this.context.log('Verifying extension is installed');
const extensions = fs.readdirSync(this.extensionsDir);
const hasExtension = extensions.some(ext => ext.startsWith('github.vscode-pull-request-github'));
Expand Down
Loading
Loading