Skip to content

Commit 3960b01

Browse files
jeanp413akosyakov
authored andcommitted
Enable integration test
1 parent d9c5674 commit 3960b01

File tree

5 files changed

+209
-10
lines changed

5 files changed

+209
-10
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
if [[ "$OSTYPE" == "darwin"* ]]; then
5+
realpath() { [[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"; }
6+
ROOT=$(dirname $(dirname $(dirname $(dirname $(realpath "$0")))))
7+
else
8+
ROOT=$(dirname $(dirname $(dirname $(dirname $(readlink -f $0)))))
9+
fi
10+
11+
cd $ROOT
12+
13+
# Tests in the extension host
14+
TEST_SCRIPT="$ROOT/test/integration/browser/out/index.js"
15+
16+
/usr/bin/env node "$TEST_SCRIPT" --workspacePath=$ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests "$@"
17+
18+
/usr/bin/env node "$TEST_SCRIPT" --workspacePath=$ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests "$@"
19+
20+
# This seems it's electron only?
21+
# /usr/bin/env node "$TEST_SCRIPT" --workspacePath=$ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out "$@"
22+
23+
/usr/bin/env node "$TEST_SCRIPT" --workspacePath=$ROOT/extensions/typescript-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/typescript-language-features --extensionTestsPath=$ROOT/extensions/typescript-language-features/out/test/unit "$@"
24+
25+
/usr/bin/env node "$TEST_SCRIPT" --workspacePath=$ROOT/extensions/markdown-language-features/test-workspace --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test "$@"
26+
27+
/usr/bin/env node "$TEST_SCRIPT" --workspacePath=$ROOT/extensions/emmet/test-workspace --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test "$@"
28+
29+
/usr/bin/env node "$TEST_SCRIPT" --workspacePath=$(mktemp -d 2>/dev/null) --enable-proposed-api=vscode.git --extensionDevelopmentPath=$ROOT/extensions/git --extensionTestsPath=$ROOT/extensions/git/out/test "$@"
30+
31+
/usr/bin/env node "$TEST_SCRIPT" --workspacePath=$(mktemp -d 2>/dev/null) --extensionDevelopmentPath=$ROOT/extensions/ipynb --extensionTestsPath=$ROOT/extensions/ipynb/out/test "$@"

resources/server/web.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,8 @@ else
77
ROOT=$(dirname $(dirname $(dirname $(readlink -f $0))))
88
fi
99

10+
export NODE_ENV=development
11+
export VSCODE_DEV=1
12+
1013
SERVER_SCRIPT="$ROOT/out/server.js"
1114
exec /usr/bin/env node "$SERVER_SCRIPT" "$@"

src/vs/server/browser/workbench/workbench.ts

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
*--------------------------------------------------------------------------------------------*/
44

55
import { isStandalone } from 'vs/base/browser/browser';
6+
import { streamToBuffer } from 'vs/base/common/buffer';
67
import { CancellationToken } from 'vs/base/common/cancellation';
7-
import { Event } from 'vs/base/common/event';
8+
import { Emitter, Event } from 'vs/base/common/event';
89
import { Schemas } from 'vs/base/common/network';
910
import { isEqual } from 'vs/base/common/resources';
10-
import { URI } from 'vs/base/common/uri';
11+
import { URI, UriComponents } from 'vs/base/common/uri';
12+
import { generateUuid } from 'vs/base/common/uuid';
1113
import { request } from 'vs/base/parts/request/browser/request';
1214
import { localize } from 'vs/nls';
1315
import { parseLogLevel } from 'vs/platform/log/common/log';
1416
import { defaultWebSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory';
1517
import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows';
16-
import { create, ICredentialsProvider, IHomeIndicator, IProductQualityChangeHandler, IWindowIndicator, IWorkbenchConstructionOptions, IWorkspace, IWorkspaceProvider } from 'vs/workbench/workbench.web.api';
18+
import { create, Disposable, ICredentialsProvider, IHomeIndicator, IProductQualityChangeHandler, IURLCallbackProvider, IWindowIndicator, IWorkbenchConstructionOptions, IWorkspace, IWorkspaceProvider } from 'vs/workbench/workbench.web.api';
1719

1820
function doCreateUri(path: string, queryValues: Map<string, string>): URI {
1921
let query: string | undefined = undefined;
@@ -159,6 +161,86 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider {
159161
}
160162
}
161163

164+
class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvider {
165+
166+
static readonly FETCH_INTERVAL = 500; // fetch every 500ms
167+
static readonly FETCH_TIMEOUT = 5 * 60 * 1000; // ...but stop after 5min
168+
169+
static readonly QUERY_KEYS = {
170+
REQUEST_ID: 'vscode-requestId',
171+
SCHEME: 'vscode-scheme',
172+
AUTHORITY: 'vscode-authority',
173+
PATH: 'vscode-path',
174+
QUERY: 'vscode-query',
175+
FRAGMENT: 'vscode-fragment'
176+
};
177+
178+
private readonly _onCallback = this._register(new Emitter<URI>());
179+
readonly onCallback = this._onCallback.event;
180+
181+
create(options?: Partial<UriComponents>): URI {
182+
const queryValues: Map<string, string> = new Map();
183+
184+
const requestId = generateUuid();
185+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId);
186+
187+
const { scheme, authority, path, query, fragment } = options ? options : { scheme: undefined, authority: undefined, path: undefined, query: undefined, fragment: undefined };
188+
189+
if (scheme) {
190+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.SCHEME, scheme);
191+
}
192+
193+
if (authority) {
194+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.AUTHORITY, authority);
195+
}
196+
197+
if (path) {
198+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.PATH, path);
199+
}
200+
201+
if (query) {
202+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.QUERY, query);
203+
}
204+
205+
if (fragment) {
206+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.FRAGMENT, fragment);
207+
}
208+
209+
// Start to poll on the callback being fired
210+
this.periodicFetchCallback(requestId, Date.now());
211+
212+
return doCreateUri('/callback', queryValues);
213+
}
214+
215+
private async periodicFetchCallback(requestId: string, startTime: number): Promise<void> {
216+
217+
// Ask server for callback results
218+
const queryValues: Map<string, string> = new Map();
219+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId);
220+
221+
const result = await request({
222+
url: doCreateUri('/fetch-callback', queryValues).toString(true)
223+
}, CancellationToken.None);
224+
225+
// Check for callback results
226+
const content = await streamToBuffer(result.stream);
227+
if (content.byteLength > 0) {
228+
try {
229+
this._onCallback.fire(URI.revive(JSON.parse(content.toString())));
230+
} catch (error) {
231+
console.error(error);
232+
}
233+
234+
return; // done
235+
}
236+
237+
// Continue fetching unless we hit the timeout
238+
if (Date.now() - startTime < PollingURLCallbackProvider.FETCH_TIMEOUT) {
239+
setTimeout(() => this.periodicFetchCallback(requestId, startTime), PollingURLCallbackProvider.FETCH_INTERVAL);
240+
}
241+
}
242+
}
243+
162244
class WorkspaceProvider implements IWorkspaceProvider {
163245

164246
static QUERY_PARAM_EMPTY_WINDOW = 'ew';
@@ -411,6 +493,7 @@ class WindowIndicator implements IWindowIndicator {
411493
windowIndicator,
412494
productQualityChangeHandler,
413495
workspaceProvider,
496+
urlCallbackProvider: new PollingURLCallbackProvider(),
414497
credentialsProvider: new LocalStorageCredentialsProvider()
415498
});
416499
})();

src/vs/server/node/server.main.ts

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { isPromiseCanceledError, onUnexpectedError, setUnexpectedErrorHandler }
1818
import { Emitter, Event } from 'vs/base/common/event';
1919
import { IDisposable } from 'vs/base/common/lifecycle';
2020
import { FileAccess, Schemas } from 'vs/base/common/network';
21-
import { join } from 'vs/base/common/path';
21+
import { dirname, join } from 'vs/base/common/path';
2222
import * as platform from 'vs/base/common/platform';
2323
import Severity from 'vs/base/common/severity';
2424
import { ReadableStreamEventPayload } from 'vs/base/common/stream';
@@ -73,7 +73,7 @@ import { RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/
7373
export type IRawURITransformerFactory = (remoteAuthority: string) => IRawURITransformer;
7474
export const IRawURITransformerFactory = createDecorator<IRawURITransformerFactory>('rawURITransformerFactory');
7575

76-
const APP_ROOT = path.join(__dirname, '..', '..', '..', '..');
76+
const APP_ROOT = dirname(FileAccess.asFileUri('', require).fsPath);
7777
const uriTransformerPath = path.join(APP_ROOT, 'out/serverUriTransformer');
7878
const rawURITransformerFactory: IRawURITransformerFactory = <any>require.__$__nodeRequire(uriTransformerPath);
7979

@@ -174,6 +174,28 @@ function serveError(req: http.IncomingMessage, res: http.ServerResponse, errorCo
174174
res.end(errorMessage);
175175
}
176176

177+
function getFirstQueryValue(parsedUrl: url.UrlWithParsedQuery, key: string) {
178+
const result = parsedUrl.query[key];
179+
return Array.isArray(result) ? result[0] : result;
180+
}
181+
182+
function getFirstQueryValues(parsedUrl: url.UrlWithParsedQuery, ignoreKeys?: string[]) {
183+
const queryValues = new Map();
184+
185+
for (const key in parsedUrl.query) {
186+
if (ignoreKeys && ignoreKeys.indexOf(key) >= 0) {
187+
continue;
188+
}
189+
190+
const value = getFirstQueryValue(parsedUrl, key);
191+
if (typeof value === 'string') {
192+
queryValues.set(key, value);
193+
}
194+
}
195+
196+
return queryValues;
197+
}
198+
177199
async function serveFile(logService: ILogService, req: http.IncomingMessage, res: http.ServerResponse, filePath: string, responseHeaders: http.OutgoingHttpHeaders = {}) {
178200
try {
179201

@@ -198,7 +220,7 @@ async function serveFile(logService: ILogService, req: http.IncomingMessage, res
198220
// Data
199221
fs.createReadStream(filePath).pipe(res);
200222
} catch (error) {
201-
logService.error(error.toString());
223+
logService.error(error);
202224
res.writeHead(404, { 'Content-Type': 'text/plain' });
203225
return res.end('Not found');
204226
}
@@ -226,6 +248,57 @@ async function handleRoot(req: http.IncomingMessage, resp: http.ServerResponse,
226248
return resp.end(entryPointContent);
227249
}
228250

251+
const mapCallbackUriToRequestId = new Map<string, string>();
252+
async function handleCallback(logService: ILogService, req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: url.UrlWithParsedQuery) {
253+
const wellKnownKeys = ['vscode-requestId', 'vscode-scheme', 'vscode-authority', 'vscode-path', 'vscode-query', 'vscode-fragment'];
254+
const [requestId, vscodeScheme, vscodeAuthority, vscodePath, vscodeQuery, vscodeFragment] = wellKnownKeys.map(key => {
255+
const value = getFirstQueryValue(parsedUrl, key);
256+
if (value) {
257+
return decodeURIComponent(value);
258+
}
259+
260+
return value;
261+
});
262+
263+
if (!requestId) {
264+
res.writeHead(400, { 'Content-Type': 'text/plain' });
265+
return res.end(`Bad request.`);
266+
}
267+
268+
// merge over additional query values that we got
269+
let query = vscodeQuery;
270+
let index = 0;
271+
getFirstQueryValues(parsedUrl, wellKnownKeys).forEach((value, key) => {
272+
if (!query) {
273+
query = '';
274+
}
275+
276+
const prefix = (index++ === 0) ? '' : '&';
277+
query += `${prefix}${key}=${value}`;
278+
});
279+
280+
// add to map of known callbacks
281+
mapCallbackUriToRequestId.set(requestId, JSON.stringify({ scheme: vscodeScheme || product.urlProtocol, authority: vscodeAuthority, path: vscodePath, query, fragment: vscodeFragment }));
282+
return serveFile(logService, req, res, FileAccess.asFileUri('vs/code/browser/workbench/callback.html', require).fsPath, { 'Content-Type': 'text/html' });
283+
}
284+
285+
async function handleFetchCallback(req: http.IncomingMessage, res: http.ServerResponse, parsedUrl: url.UrlWithParsedQuery) {
286+
const requestId = getFirstQueryValue(parsedUrl, 'vscode-requestId');
287+
if (!requestId) {
288+
res.writeHead(400, { 'Content-Type': 'text/plain' });
289+
return res.end(`Bad request.`);
290+
}
291+
292+
const knownCallbackUri = mapCallbackUriToRequestId.get(requestId);
293+
if (knownCallbackUri) {
294+
mapCallbackUriToRequestId.delete(requestId);
295+
}
296+
297+
res.writeHead(200, { 'Content-Type': 'text/json' });
298+
return res.end(knownCallbackUri);
299+
}
300+
301+
229302
interface ServerParsedArgs extends NativeParsedArgs {
230303
port?: string
231304
}
@@ -253,14 +326,15 @@ export interface IServerOptions {
253326
}
254327

255328
export async function main(options: IServerOptions): Promise<void> {
256-
const devMode = !!process.env['VSCODE_DEV'];
257329
const connectionToken = generateUuid();
258330

259331
const parsedArgs = parseArgs(process.argv, SERVER_OPTIONS);
260332
parsedArgs['user-data-dir'] = URI.file(path.join(os.homedir(), product.dataFolderName)).fsPath;
261333
const productService = { _serviceBrand: undefined, ...product };
262334
const environmentService = new NativeEnvironmentService(parsedArgs, productService);
263335

336+
const devMode = !environmentService.isBuilt;
337+
264338
// see src/vs/code/electron-main/main.ts#142
265339
const bufferLogService = new BufferLogService();
266340
const logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentService)), bufferLogService]);
@@ -613,6 +687,15 @@ export async function main(options: IServerOptions): Promise<void> {
613687
if (pathname === '/') {
614688
return handleRoot(req, res, devMode ? options.mainDev || WEB_MAIN_DEV : options.main || WEB_MAIN, environmentService);
615689
}
690+
691+
if (pathname === '/callback') {
692+
return handleCallback(logService, req, res, parsedUrl);
693+
}
694+
695+
if (pathname === '/fetch-callback') {
696+
return handleFetchCallback(req, res, parsedUrl);
697+
}
698+
616699
if (pathname === '/manifest.json') {
617700
res.writeHead(200, { 'Content-Type': 'application/json' });
618701
return res.end(JSON.stringify({
@@ -634,7 +717,6 @@ export async function main(options: IServerOptions): Promise<void> {
634717
}
635718
//#region static end
636719

637-
// TODO uri callbacks ?
638720
logService.error(`${req.method} ${req.url} not found`);
639721
return serveError(req, res, 404, 'Not found.');
640722
} catch (error) {

test/integration/browser/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith
5656
const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","5f19eee5dc9588ca96192f89587b5878b7d7180d"],["skipWelcome","true"]]`;
5757

5858
if (path.extname(testWorkspaceUri) === '.code-workspace') {
59-
await page.goto(`${endpoint.href}&workspace=${testWorkspaceUri}&payload=${payloadParam}`);
59+
await page.goto(`${endpoint.href}?workspace=${testWorkspaceUri}&payload=${payloadParam}`);
6060
} else {
61-
await page.goto(`${endpoint.href}&folder=${testWorkspaceUri}&payload=${payloadParam}`);
61+
await page.goto(`${endpoint.href}?folder=${testWorkspaceUri}&payload=${payloadParam}`);
6262
}
6363

6464
await page.exposeFunction('codeAutomationLog', (type: string, args: any[]) => {

0 commit comments

Comments
 (0)