Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use wslpath to resolve Windows paths #200

Merged
merged 8 commits into from
Mar 14, 2022
9 changes: 5 additions & 4 deletions src/chrome-finder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {execSync, execFileSync} from 'child_process';
import escapeRegExp = require('escape-string-regexp');
const log = require('lighthouse-logger');

import {getLocalAppDataPath, ChromePathNotSetError} from './utils';
import {getWSLLocalAppDataPath, toWSLPath, ChromePathNotSetError} from './utils';

const newLineRegex = /\r?\n/;

Expand Down Expand Up @@ -175,9 +175,10 @@ export function linux() {

export function wsl() {
// Manually populate the environment variables assuming it's the default config
process.env.LOCALAPPDATA = getLocalAppDataPath(`${process.env.PATH}`);
process.env.PROGRAMFILES = '/mnt/c/Program Files';
process.env['PROGRAMFILES(X86)'] = '/mnt/c/Program Files (x86)';
process.env.LOCALAPPDATA = getWSLLocalAppDataPath(`${process.env.PATH}`);
process.env.PROGRAMFILES = toWSLPath('C:/Program Files', '/mnt/c/Program Files');
process.env['PROGRAMFILES(X86)'] =
toWSLPath('C:/Program Files (x86)', '/mnt/c/Program Files (x86)');

return win32();
}
Expand Down
4 changes: 2 additions & 2 deletions src/chrome-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as net from 'net';
import * as chromeFinder from './chrome-finder';
import {getRandomPort} from './random-port';
import {DEFAULT_FLAGS} from './flags';
import {makeTmpDir, defaults, delay, getPlatform, toWinDirFormat, InvalidUserDataDirectoryError, UnsupportedPlatformError, ChromeNotInstalledError} from './utils';
import {makeTmpDir, defaults, delay, getPlatform, toWin32Path, InvalidUserDataDirectoryError, UnsupportedPlatformError, ChromeNotInstalledError} from './utils';
import {ChildProcess} from 'child_process';
const log = require('lighthouse-logger');
const spawn = childProcess.spawn;
Expand Down Expand Up @@ -158,7 +158,7 @@ class Launcher {
if (!this.useDefaultProfile) {
// Place Chrome profile in a custom location we'll rm -rf later
// If in WSL, we need to use the Windows format
flags.push(`--user-data-dir=${isWsl ? toWinDirFormat(this.userDataDir) : this.userDataDir}`);
flags.push(`--user-data-dir=${isWsl ? toWin32Path(this.userDataDir) : this.userDataDir}`);
}

flags.push(...this.chromeFlags);
Expand Down
37 changes: 33 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
'use strict';

import {join} from 'path';
import {execSync} from 'child_process';
import {execSync, execFileSync} from 'child_process';
import {mkdirSync} from 'fs';
import isWsl = require('is-wsl');

Expand Down Expand Up @@ -65,16 +65,17 @@ export function makeTmpDir() {
return makeUnixTmpDir();
case 'wsl':
// We populate the user's Windows temp dir so the folder is correctly created later
process.env.TEMP = getLocalAppDataPath(`${process.env.PATH}`);
process.env.TEMP = getWSLLocalAppDataPath(`${process.env.PATH}`);
case 'win32':
return makeWin32TmpDir();
default:
throw new UnsupportedPlatformError();
}
}

export function toWinDirFormat(dir: string = ''): string {
function toWinDirFormat(dir: string = ''): string {
const results = /\/mnt\/([a-z])\//.exec(dir);

if (!results) {
return dir;
}
Expand All @@ -84,13 +85,41 @@ export function toWinDirFormat(dir: string = ''): string {
.replace(/\//g, '\\');
}

export function getLocalAppDataPath(path: string): string {
export function toWin32Path(dir: string = ''): string {
if (/[a-z]:\\/iu.test(dir)) {
return dir;
}

try {
return execFileSync('wslpath', ['-w', dir]).toString().trim();
} catch {
return toWinDirFormat(dir);
}
}

export function toWSLPath(dir: string, fallback: string): string {
try {
return execFileSync('wslpath', ['-u', dir]).toString().trim();
} catch {
return fallback;
}
}

function getLocalAppDataPath(path: string): string {
const userRegExp = /\/mnt\/([a-z])\/Users\/([^\/:]+)\/AppData\//;
const results = userRegExp.exec(path) || [];

return `/mnt/${results[1]}/Users/${results[2]}/AppData/Local`;
}

export function getWSLLocalAppDataPath(path: string): string {
const userRegExp = /\/([a-z])\/Users\/([^\/:]+)\/AppData\//;
const results = userRegExp.exec(path) || [];

return toWSLPath(
`${results[1]}:\\Users\\${results[2]}\\AppData\\Local`, getLocalAppDataPath(path));
}

function makeUnixTmpDir() {
return execSync('mktemp -d -t lighthouse.XXXXXXX').toString().trim();
}
Expand Down
108 changes: 94 additions & 14 deletions test/utils-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,104 @@
'use strict';

import * as assert from 'assert';
import {toWinDirFormat, getLocalAppDataPath} from '../src/utils';
import { toWin32Path, toWSLPath, getWSLLocalAppDataPath } from '../src/utils';
import * as sinon from 'sinon';
import * as child_process from 'child_process';

describe('WSL path format to Windows', () => {
G-Rath marked this conversation as resolved.
Show resolved Hide resolved
it('transforms basic path', () => {
const wsl = '/mnt/c/Users/user1/AppData/';
const windows = 'C:\\Users\\user1\\AppData\\';
assert.strictEqual(toWinDirFormat(wsl), windows);
});
const execFileSyncStub = sinon.stub(child_process, 'execFileSync').callThrough();

const asBuffer = (str: string): Buffer => Buffer.from(str, 'utf-8');

describe('toWin32Path', () => {
beforeEach(() => execFileSyncStub.reset());

it('calls toWin32Path -w', () => {
execFileSyncStub.returns(asBuffer(''));

toWin32Path('');

assert.ok(execFileSyncStub.calledWith('wslpath', ['-w', '']));
})

describe('when the path is already in Windows format', () => {
it('returns early', () => {
execFileSyncStub.returns(asBuffer(''));

it('transforms if drive letter is different than c', () => {
const wsl = '/mnt/d/Users/user1/AppData';
const windows = 'D:\\Users\\user1\\AppData';
assert.strictEqual(toWinDirFormat(wsl), windows);
assert.strictEqual(toWin32Path('D:\\'), 'D:\\');
assert.strictEqual(toWin32Path('C:\\'), 'C:\\');

assert.ok(execFileSyncStub.notCalled);
});
})

describe('when wslpath is not available', () => {
beforeEach(() => execFileSyncStub.throws(new Error('oh noes!')));

it('falls back to the toWinDirFormat method', () => {
const wsl = '/mnt/c/Users/user1/AppData/';
const windows = 'C:\\Users\\user1\\AppData\\';

assert.strictEqual(toWin32Path(wsl), windows);
});

it('supports the drive letter not being C', () => {
const wsl = '/mnt/d/Users/user1/AppData';
const windows = 'D:\\Users\\user1\\AppData';

assert.strictEqual(toWin32Path(wsl), windows);
})
});
})

describe('toWSLPath', () => {
beforeEach(() => execFileSyncStub.reset());

it('calls wslpath -u', () => {
execFileSyncStub.returns(asBuffer(''));

toWSLPath('', '');

assert.ok(execFileSyncStub.calledWith('wslpath', ['-u', '']));
})

it('trims off the trailing newline', () => {
execFileSyncStub.returns(asBuffer('the-path\n'));

assert.strictEqual(toWSLPath('', ''), 'the-path');
})

describe('when wslpath is not available', () => {
beforeEach(() => execFileSyncStub.throws(new Error('oh noes!')));

it('uses the fallback path', () => {
assert.strictEqual(
toWSLPath('C:/Program Files', '/mnt/c/Program Files'),
'/mnt/c/Program Files'
);
})
})
})

describe('getWSLLocalAppDataPath', () => {
beforeEach(() => execFileSyncStub.reset());

it('transforms it to a Linux path using wslpath', () => {
execFileSyncStub.returns(asBuffer('/c/folder/'));

it('getLocalAppDataPath returns a correct path', () => {
const path = '/mnt/c/Users/user1/.bin:/mnt/c/Users/user1:/mnt/c/Users/user1/AppData/';
const appDataPath = '/mnt/c/Users/user1/AppData/Local';
assert.strictEqual(getLocalAppDataPath(path), appDataPath);

assert.strictEqual(getWSLLocalAppDataPath(path), '/c/folder/');
assert.ok(execFileSyncStub.calledWith('wslpath', ['-u', 'c:\\Users\\user1\\AppData\\Local']));
});

describe('when wslpath is not available', () => {
beforeEach(() => execFileSyncStub.throws(new Error('oh noes!')));

it('falls back to the getLocalAppDataPath method', () => {
const path = '/mnt/c/Users/user1/.bin:/mnt/c/Users/user1:/mnt/c/Users/user1/AppData/';
const appDataPath = '/mnt/c/Users/user1/AppData/Local';

assert.strictEqual(getWSLLocalAppDataPath(path), appDataPath);
});
});
});