Skip to content

Commit

Permalink
[CI] [browser tests] Fix find-replace test suite
Browse files Browse the repository at this point in the history
That test suite was still experiencing intermittent failures. Playing around with it,
I found-out that there were sometimes exceptions thrown when we do not let the
language server finish its startup, before we close the editor. The LS would then start
its initialization from scratch when the editor was re-opened, increasing the chances we
would cause issues. The solution: When opening an editor, wait until the LS has finished
starting before proceeding. This causes a small delay the first time, but is instantaneous
the following times.

Also, added a new browser test file: explorer-open-close.spec.js. I suspected that there
was some flakiness in the find-replace suite, potentially caused by the mix of opening and
closing both the explorer and an editor. I was not able to find anything but with this new
test file we might, if there's any, now or later.

Signed-off-by: Marc Dumais <[email protected]>
  • Loading branch information
marcdumais-work committed Mar 20, 2023
1 parent afdae63 commit fa1db70
Show file tree
Hide file tree
Showing 2 changed files with 212 additions and 23 deletions.
155 changes: 155 additions & 0 deletions examples/api-tests/src/explorer-open-close.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// *****************************************************************************
// Copyright (C) 2023 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 WITH Classpath-exception-2.0
// *****************************************************************************

// @ts-check
describe('Explorer and Editor - open and close', function () {
this.timeout(90_000);
const { assert } = chai;

const { DisposableCollection } = require('@theia/core/lib/common/disposable');
const { EditorManager } = require('@theia/editor/lib/browser/editor-manager');
const { WorkspaceService } = require('@theia/workspace/lib/browser/workspace-service');
const { FileNavigatorContribution } = require('@theia/navigator/lib/browser/navigator-contribution');
const { ApplicationShell } = require('@theia/core/lib/browser/shell/application-shell');
const { HostedPluginSupport } = require('@theia/plugin-ext/lib/hosted/browser/hosted-plugin');
const { ProgressStatusBarItem } = require('@theia/core/lib/browser/progress-status-bar-item');
const { EXPLORER_VIEW_CONTAINER_ID } = require('@theia/navigator/lib/browser/navigator-widget-factory');
const { MonacoEditor } = require('@theia/monaco/lib/browser/monaco-editor');
const container = window.theia.container;
const editorManager = container.get(EditorManager);
const workspaceService = container.get(WorkspaceService);
const navigatorContribution = container.get(FileNavigatorContribution);
const shell = container.get(ApplicationShell);
const rootUri = workspaceService.tryGetRoots()[0].resource;
const pluginService = container.get(HostedPluginSupport);
const progressStatusBarItem = container.get(ProgressStatusBarItem);


const fileUri = rootUri.resolve('webpack.config.js');
const toTearDown = new DisposableCollection();

function pause(ms = 500) {
console.debug(`pause test for: ${ms} ms`);
return new Promise(resolve => setTimeout(resolve, ms));
}

before(async () => {
await pluginService.didStart;
await editorManager.closeAll({ save: false });
});

afterEach(async () => {
await editorManager.closeAll({ save: false });
await navigatorContribution.closeView();
});

after(async () => {
toTearDown.dispose();
});

for (var i = 0; i < 5; i++) {
let ordering = 0;
it('Open/Close explorer and editor - ordering: ' + ordering++ + ', iteration #' + i, async function () {
await openExplorer();
await openEditor();
await closeEditor();
await closeExplorer();
});

it('Open/Close explorer and editor - ordering: ' + ordering++ + ', iteration #' + i, async function () {
await openExplorer();
await openEditor();
await closeExplorer();
await closeEditor();
});

it('Open/Close editor, explorer - ordering: ' + ordering++ + ', iteration - #' + i, async function () {
await openEditor();
await openExplorer();
await closeEditor();
await closeExplorer();
});

it('Open/Close editor, explorer - ordering: ' + ordering++ + ', iteration - #' + i, async function () {
await openEditor();
await openExplorer();
await closeExplorer();
await closeEditor();
});

it('Open/Close explorer #' + i, async function () {
await openExplorer();
await closeExplorer();
});
}

it('open/close explorer in quick succession', async function () {
for (let i = 0; i < 20; i++) {
await openExplorer();
await closeExplorer();
}
});

it('open/close editor in quick succession', async function () {
await openExplorer();
for (let i = 0; i < 20; i++) {
await openEditor();
await closeEditor();
}
});

async function openExplorer() {
await navigatorContribution.openView({ activate: true });
const widget = await shell.revealWidget(EXPLORER_VIEW_CONTAINER_ID);
assert.isDefined(widget, 'Explorer widget should exist');
}
async function closeExplorer() {
await navigatorContribution.closeView();
assert.isUndefined(await shell.revealWidget(EXPLORER_VIEW_CONTAINER_ID), 'Explorer widget should not exist');
}

async function openEditor() {
await editorManager.open(fileUri, { mode: 'activate' });
await waitLanguageServerReady();
const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
assert.isDefined(activeEditor);
assert.equal(activeEditor.uri.resolveToAbsolute().toString(), fileUri.resolveToAbsolute().toString());
}

async function closeEditor() {
await editorManager.closeAll({ save: false });
const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
assert.isUndefined(activeEditor);
}

async function waitLanguageServerReady() {
// quite a bit of jitter in the "Initializing LS" status bar entry,
// so we want to read a few times in a row that it's done (undefined)
const MAX_N = 5
let n = MAX_N;
while (n > 0) {
await pause(1);
if (progressStatusBarItem.currentProgress) {
n = MAX_N;
} else {
n--;
}
if (n < MAX_N) {
console.debug('n = ' + n);
}
}
}
});
80 changes: 57 additions & 23 deletions examples/api-tests/src/find-replace.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

// @ts-check
describe('Find and Replace', function () {
this.timeout(5_000);
this.timeout(20_000);
const { assert } = chai;

const { animationFrame } = require('@theia/core/lib/browser/browser');
Expand All @@ -29,6 +29,10 @@ describe('Find and Replace', function () {
const { ContextKeyService } = require('@theia/core/lib/browser/context-key-service');
const { FileNavigatorContribution } = require('@theia/navigator/lib/browser/navigator-contribution');
const { ApplicationShell } = require('@theia/core/lib/browser/shell/application-shell');
const { HostedPluginSupport } = require('@theia/plugin-ext/lib/hosted/browser/hosted-plugin');
const { ProgressStatusBarItem } = require('@theia/core/lib/browser/progress-status-bar-item');
const { EXPLORER_VIEW_CONTAINER_ID } = require('@theia/navigator/lib/browser/navigator-widget-factory');
const { MonacoEditor } = require('@theia/monaco/lib/browser/monaco-editor');
const container = window.theia.container;
const editorManager = container.get(EditorManager);
const workspaceService = container.get(WorkspaceService);
Expand All @@ -38,10 +42,17 @@ describe('Find and Replace', function () {
const navigatorContribution = container.get(FileNavigatorContribution);
const shell = container.get(ApplicationShell);
const rootUri = workspaceService.tryGetRoots()[0].resource;
const fileUri = rootUri.resolve('webpack.config.js');
const pluginService = container.get(HostedPluginSupport);
const progressStatusBarItem = container.get(ProgressStatusBarItem);
const fileUri = rootUri.resolve('../api-tests/test-ts-workspace/demo-file.ts');

const toTearDown = new DisposableCollection();

function pause(ms = 500) {
console.debug(`pause test for: ${ms} ms`);
return new Promise(resolve => setTimeout(resolve, ms));
}

/**
* @template T
* @param {() => Promise<T> | T} condition
Expand All @@ -57,32 +68,23 @@ describe('Find and Replace', function () {
});
}

function pause(ms = 500) {
console.debug(`pause test for: ${ms} ms`);
return new Promise(resolve => setTimeout(resolve, ms));
}


before(() => {
shell.leftPanelHandler.collapse();
before(async () => {
await pluginService.didStart;
await shell.leftPanelHandler.collapse();
await editorManager.closeAll({ save: false });
});

beforeEach(async function () {
await navigatorContribution.closeView();
await pause();
await editorManager.closeAll({ save: false });
await pause();
});

afterEach(async () => {
toTearDown.dispose();
await navigatorContribution.closeView();
await pause();
await editorManager.closeAll({ save: false });
});

after(() => {
shell.leftPanelHandler.collapse();
after(async () => {
await shell.leftPanelHandler.collapse();
toTearDown.dispose();
});

/**
Expand All @@ -109,28 +111,60 @@ describe('Find and Replace', function () {

for (const command of [CommonCommands.FIND, CommonCommands.REPLACE]) {
it(command.label + ' in the active editor', async function () {
await navigatorContribution.openView({ activate: true });
await openExplorer();

await editorManager.open(fileUri, { mode: 'activate' });
await openEditor();

await assertEditorFindReplace(command);
});

it(command.label + ' in the active explorer without the current editor', async function () {
await navigatorContribution.openView({ activate: true });
await openExplorer();

// should not throw
await commands.executeCommand(command.id);
});

it(command.label + ' in the active explorer with the current editor', async function () {
await editorManager.open(fileUri, { mode: 'activate' });
await openEditor();

await navigatorContribution.openView({ activate: true });
await openExplorer();

await assertEditorFindReplace(command);
});

}

async function openExplorer() {
await navigatorContribution.openView({ activate: true });
const widget = await shell.revealWidget(EXPLORER_VIEW_CONTAINER_ID);
assert.isDefined(widget, 'Explorer widget should exist');
}

async function openEditor() {
await editorManager.open(fileUri, { mode: 'activate' });
await waitLanguageServerReady();
const activeEditor = /** @type {MonacoEditor} */ MonacoEditor.get(editorManager.activeEditor);
assert.isDefined(activeEditor);
// @ts-ignore
assert.equal(activeEditor.uri.resolveToAbsolute().toString(), fileUri.resolveToAbsolute().toString());
}

async function waitLanguageServerReady() {
// quite a bit of jitter in the "Initializing LS" status bar entry,
// so we want to read a few times in a row that it's done (undefined)
const MAX_N = 5
let n = MAX_N;
while (n > 0) {
await pause(1);
if (progressStatusBarItem.currentProgress) {
n = MAX_N;
} else {
n--;
}
if (n < 5) {
console.debug('n = ' + n);
}
}
}
});

0 comments on commit fa1db70

Please sign in to comment.