Skip to content

Commit 56f9d17

Browse files
HeikoKlarefedejeanne
authored andcommitted
Make completion proposal calculation test time-independent #906
The test cases for completion proposals in CompletionTest depend on specific timing behavior of a test proposal processor. On one hand, this is prone to errors if proper timing is not given on the test environment. On the other hand, the tests run unnecessarily long because of waiting long enough. This change replaces the fixed-time waiting of the LongRunningBarContentAssistProcessor with an explicit trigger that finalizes the content calculation as soon as some barrier is reached within the tests. In addition, the long-running processor is only enabled on demand and will be finalized whenever a test case was executed to avoid that the processor thread is leaking to the next test case. Contributes to #906
1 parent f706c26 commit 56f9d17

File tree

3 files changed

+119
-77
lines changed

3 files changed

+119
-77
lines changed

tests/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/CompletionTest.java

Lines changed: 94 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.List;
3636
import java.util.Queue;
3737
import java.util.Set;
38+
import java.util.function.BooleanSupplier;
3839
import java.util.stream.Collectors;
3940

4041
import org.junit.jupiter.api.AfterEach;
@@ -87,8 +88,8 @@ public class CompletionTest extends AbstratGenericEditorTest {
8788
@DisabledOnOs(value = OS.MAC, disabledReason = "test fails on Mac, see https://github.com/eclipse-platform/eclipse.platform.ui/issues/906")
8889
public void testCompletion() throws Exception {
8990
editor.selectAndReveal(3, 0);
90-
this.completionShell = openContentAssist(true);
91-
final Table completionProposalList = findCompletionSelectionControl(completionShell);
91+
Shell shell = openContentAssistWithLongRunningProposalComputation();
92+
final Table completionProposalList = findCompletionSelectionControl(shell);
9293
checkCompletionContent(completionProposalList);
9394
// TODO find a way to actually trigger completion and verify result against
9495
// Editor content
@@ -102,7 +103,7 @@ public void testDefaultContentAssistBug570488() throws Exception {
102103
TestLogListener listener= new TestLogListener();
103104
log.addLogListener(listener);
104105
createAndOpenFile("Bug570488.txt", "bar 'bar'");
105-
openContentAssist(false);
106+
assertNull(openContentAssist(), "No shell is expected to open");
106107
runEventLoop(Display.getCurrent(), 0);
107108
assertFalse(listener.messages.stream().anyMatch(s -> s.matches(IStatus.ERROR)), "There are errors in the log");
108109
log.removeLogListener(listener);
@@ -120,8 +121,8 @@ public void testCompletionService() throws Exception {
120121
new Hashtable<>(Collections.singletonMap("contentType", "org.eclipse.ui.genericeditor.tests.content-type")));
121122
runEventLoop(Display.getCurrent(), 0);
122123
editor.selectAndReveal(3, 0);
123-
this.completionShell= openContentAssist(true);
124-
final Table completionProposalList= findCompletionSelectionControl(completionShell);
124+
Shell shell = openContentAssistWithLongRunningProposalComputation();
125+
final Table completionProposalList = findCompletionSelectionControl(shell);
125126
assertTrue(service.called, "Service was not called!");
126127
checkCompletionContent(completionProposalList);
127128
registration.unregister();
@@ -132,127 +133,149 @@ public void testCompletionService() throws Exception {
132133
public void testCompletionUsingViewerSelection() throws Exception {
133134
editor.getDocumentProvider().getDocument(editor.getEditorInput()).set("abc");
134135
editor.selectAndReveal(0, 3);
135-
this.completionShell= openContentAssist(true);
136-
final Table completionProposalList = findCompletionSelectionControl(completionShell);
137-
assertTrue(waitForCondition(completionProposalList.getDisplay(), 5000, () -> {
136+
final Shell shell = openContentAssist();
137+
assertNotNull(shell, "Shell is expected to open for completion proposals");
138+
final Table completionProposalList = findCompletionSelectionControl(shell);
139+
waitForProposalRelatedCondition("Proposal list did not contain expected item 'ABC'", completionProposalList,
140+
() -> Arrays.stream(completionProposalList.getItems()).map(TableItem::getText).anyMatch("ABC"::equals), 5_000);
141+
}
142+
143+
private static void waitForProposalRelatedCondition(String expectedListContentDescription,
144+
Table completionProposalList, BooleanSupplier condition, int timeoutInMsec) {
145+
boolean result = waitForCondition(completionProposalList.getDisplay(), timeoutInMsec, () -> {
138146
assertFalse(completionProposalList.isDisposed(), "Completion proposal list was unexpectedly disposed");
139-
return Arrays.stream(completionProposalList.getItems()).map(TableItem::getText).anyMatch("ABC"::equals);
140-
}), "Proposal list did not contain expected item: ABC");
147+
return condition.getAsBoolean();
148+
});
149+
assertTrue(result, expectedListContentDescription + " but contained: "
150+
+ Arrays.toString(completionProposalList.getItems()));
141151
}
142-
152+
143153
@Test
144154
public void testEnabledWhenCompletion() throws Exception {
145-
// Confirm that when disabled, a completion shell is present
155+
// Confirm that when disabled, a completion shell is not present
146156
EnabledPropertyTester.setEnabled(false);
147157
createAndOpenFile("enabledWhen.txt", "bar 'bar'");
148158
editor.selectAndReveal(3, 0);
149-
assertNull(openContentAssist(false), "A new shell was found");
159+
assertNull(openContentAssist(), "No shell is expected to open");
150160
cleanFileAndEditor();
151161

152162
// Confirm that when enabled, a completion shell is present
153163
EnabledPropertyTester.setEnabled(true);
154164
createAndOpenFile("enabledWhen.txt", "bar 'bar'");
155-
editor.selectAndReveal(3, 0);
156-
assertNotNull(openContentAssist(true));
165+
editor.selectAndReveal(3, 0);
166+
assertNotNull(openContentAssist(), "Shell is expected to open for completion proposals");
167+
}
168+
169+
private Shell openContentAssistWithLongRunningProposalComputation() {
170+
LongRunningBarContentAssistProcessor.enable();
171+
Shell shell = openContentAssist();
172+
assertNotNull(shell, "Shell is expected to open for completion proposals");
173+
return shell;
157174
}
158175

159-
private Shell openContentAssist(boolean expectShell) {
176+
private Shell openContentAssist() {
160177
ContentAssistAction action = (ContentAssistAction) editor.getAction(ITextEditorActionConstants.CONTENT_ASSIST);
161178
action.update();
162-
final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
163-
action.run(); //opens shell
164-
Shell shell= findNewShell(beforeShells, editor.getSite().getShell().getDisplay(),expectShell);
165-
runEventLoop(PlatformUI.getWorkbench().getDisplay(), 100); // can dispose shell when focus lost during debugging
179+
final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells())
180+
.filter(Shell::isVisible).collect(Collectors.toSet());
181+
action.run();
182+
Shell shell = findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
183+
runEventLoop(PlatformUI.getWorkbench().getDisplay(), 100);
184+
if (shell != null) {
185+
this.completionShell = shell;
186+
}
166187
return shell;
167188
}
168189

169190
/**
170191
* Checks that completion behaves as expected:
171192
* 1. Computing is shown instantaneously
172193
* 2. 1st proposal shown instantaneously
173-
* 3. 2s later, 2nd proposal is shown
194+
* 3. Calculation finishes when the test explicitly releases it
174195
* @param completionProposalList the completion list
175196
*/
176197
private void checkCompletionContent(final Table completionProposalList) {
177-
// should be instantaneous, but happens to go asynchronous on CI so let's allow a wait
178-
assertTrue(waitForCondition(completionProposalList.getDisplay(), 200, () -> {
179-
assertFalse(completionProposalList.isDisposed(), "Completion proposal list was unexpectedly disposed");
180-
return completionProposalList.getItemCount() == 2 && completionProposalList.getItem(1).getData() != null;
181-
}), "Proposal list did not show two initial items");
182-
assertTrue(isComputingInfoEntry(completionProposalList.getItem(0)), "Missing computing info entry in proposal list");
198+
waitForProposalRelatedCondition("Proposal list should show two initial items", completionProposalList,
199+
() -> completionProposalList.getItemCount() == 2
200+
&& completionProposalList.getItem(1).getData() != null,
201+
200);
202+
assertTrue(isComputingInfoEntry(completionProposalList.getItem(0)), "Missing computing info entry");
183203
final TableItem initialProposalItem = completionProposalList.getItem(1);
184-
final String initialProposalString = ((ICompletionProposal)initialProposalItem.getData()).getDisplayString();
185-
assertThat("Unexpected initial proposal item",
204+
final String initialProposalString = ((ICompletionProposal) initialProposalItem.getData()).getDisplayString();
205+
assertThat("Unexpected initial proposal item",
186206
BAR_CONTENT_ASSIST_PROPOSAL, endsWith(initialProposalString));
187207
completionProposalList.setSelection(initialProposalItem);
188-
// asynchronous
189-
assertTrue(waitForCondition(completionProposalList.getDisplay(), LongRunningBarContentAssistProcessor.DELAY * 2,
190-
() -> {
191-
assertFalse(completionProposalList.isDisposed(),
192-
"Completion proposal list was unexpectedly disposed");
193-
return !isComputingInfoEntry(completionProposalList.getItem(0))
194-
&& completionProposalList.getItemCount() == 2;
195-
}), "Proposal list did not show two items after finishing computing");
208+
209+
LongRunningBarContentAssistProcessor.finish();
210+
waitForProposalRelatedCondition("Proposal list should contain two items", completionProposalList,
211+
() -> !isComputingInfoEntry(completionProposalList.getItem(0))
212+
&& completionProposalList.getItemCount() == 2,
213+
5_000);
196214
final TableItem firstCompletionProposalItem = completionProposalList.getItem(0);
197215
final TableItem secondCompletionProposalItem = completionProposalList.getItem(1);
198-
String firstCompletionProposalText = ((ICompletionProposal)firstCompletionProposalItem.getData()).getDisplayString();
199-
String secondCompletionProposalText = ((ICompletionProposal)secondCompletionProposalItem.getData()).getDisplayString();
216+
String firstCompletionProposalText = ((ICompletionProposal) firstCompletionProposalItem.getData()).getDisplayString();
217+
String secondCompletionProposalText = ((ICompletionProposal) secondCompletionProposalItem.getData()).getDisplayString();
200218
assertThat("Unexpected first proposal item", BAR_CONTENT_ASSIST_PROPOSAL, endsWith(firstCompletionProposalText));
201-
assertThat("Unexpected second proposal item", LONG_RUNNING_BAR_CONTENT_ASSIST_PROPOSAL, endsWith(secondCompletionProposalText));
202-
String selectedProposalString = ((ICompletionProposal)completionProposalList.getSelection()[0].getData()).getDisplayString();
203-
assertEquals(initialProposalString, selectedProposalString, "Addition of completion proposal should keep selection");
219+
assertThat("Unexpected second proposal item", LONG_RUNNING_BAR_CONTENT_ASSIST_PROPOSAL,
220+
endsWith(secondCompletionProposalText));
221+
String selectedProposalString = ((ICompletionProposal) completionProposalList.getSelection()[0].getData())
222+
.getDisplayString();
223+
assertEquals(initialProposalString, selectedProposalString,
224+
"Addition of completion proposal should keep selection");
204225
}
205-
226+
206227
private static boolean isComputingInfoEntry(TableItem item) {
207228
return item.getText().contains("Computing");
208229
}
209230

210-
public static Shell findNewShell(Set<Shell> beforeShells, Display display, boolean expectShell) {
231+
public static Shell findNewShell(Set<Shell> beforeShells, Display display) {
211232
List<Shell> afterShells = Arrays.stream(display.getShells())
212233
.filter(Shell::isVisible)
213234
.filter(shell -> !beforeShells.contains(shell))
214235
.toList();
215-
if (expectShell) {
216-
assertEquals(1, afterShells.size(), "No new shell found");
217-
}
236+
assertTrue(afterShells.size() <= 1, "More than one new shell was found");
218237
return afterShells.isEmpty() ? null : afterShells.get(0);
219238
}
220239

221240
@Test
222241
@DisabledOnOs(value = OS.MAC, disabledReason = "test fails on Mac, see https://github.com/eclipse-platform/eclipse.platform.ui/issues/906")
223242
public void testCompletionFreeze_bug521484() throws Exception {
224243
editor.selectAndReveal(3, 0);
225-
this.completionShell=openContentAssist(true);
226-
final Table completionProposalList = findCompletionSelectionControl(this.completionShell);
227-
// should be instantaneous, but happens to go asynchronous on CI so let's allow a wait
228-
assertTrue(waitForCondition(completionProposalList.getDisplay(), 200, () -> {
229-
assertFalse(completionProposalList.isDisposed(), "Completion proposal list was unexpectedly disposed");
230-
return completionProposalList.getItemCount() == 2;
231-
}), "Proposal list did not show two items");
244+
final Shell shell = openContentAssistWithLongRunningProposalComputation();
245+
assertNotNull(shell, "Shell is expected to open for completion proposals");
246+
final Table completionProposalList = findCompletionSelectionControl(shell);
247+
waitForProposalRelatedCondition("Proposal list should show two items", completionProposalList,
248+
() -> completionProposalList.getItemCount() == 2, 200);
232249
assertTrue(isComputingInfoEntry(completionProposalList.getItem(0)), "Missing computing info entry");
233-
// Some processors are long running, moving cursor can cause freeze (bug 521484)
234-
// asynchronous
235250
long timestamp = System.currentTimeMillis();
236251
emulatePressLeftArrowKey();
237-
sleep(editor.getSite().getShell().getDisplay(), 200); //give time to process events
252+
sleep(editor.getSite().getShell().getDisplay(), 200);
238253
long processingDuration = System.currentTimeMillis() - timestamp;
239-
assertTrue(processingDuration < LongRunningBarContentAssistProcessor.DELAY, "UI Thread frozen for " + processingDuration + "ms");
254+
assertTrue(processingDuration < LongRunningBarContentAssistProcessor.TIMEOUT_MSEC,
255+
"UI Thread frozen for " + processingDuration + "ms");
240256
}
241257

242258
@Test
243259
@DisabledOnOs(value = OS.MAC, disabledReason = "test fails on Mac, see https://github.com/eclipse-platform/eclipse.platform.ui/issues/906")
244260
public void testMoveCaretBackUsesAllProcessors_bug522255() throws Exception {
245-
testCompletion();
261+
editor.selectAndReveal(3, 0);
262+
Shell shell = openContentAssistWithLongRunningProposalComputation();
263+
final Table completionProposalList = findCompletionSelectionControl(shell);
264+
checkCompletionContent(completionProposalList);
265+
LongRunningBarContentAssistProcessor.enable();
246266
emulatePressLeftArrowKey();
247-
final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
267+
final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells())
268+
.filter(Shell::isVisible).collect(Collectors.toSet());
248269
sleep(editor.getSite().getShell().getDisplay(), 200);
249-
this.completionShell= findNewShell(beforeShells, editor.getSite().getShell().getDisplay(), true);
250-
final Table completionProposalList = findCompletionSelectionControl(this.completionShell);
251-
checkCompletionContent(completionProposalList);
270+
assertTrue(shell.isDisposed(), "Completion proposal shell should be disposed after moving the cursor");
271+
this.completionShell = findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
272+
assertNotNull(completionShell, "Shell is expected to open for completion proposals");
273+
final Table newCompletionProposalList = findCompletionSelectionControl(completionShell);
274+
checkCompletionContent(newCompletionProposalList);
252275
}
253276

254277
private void emulatePressLeftArrowKey() {
255-
editor.selectAndReveal(((ITextSelection)editor.getSelectionProvider().getSelection()).getOffset() - 1, 0);
278+
editor.selectAndReveal(((ITextSelection) editor.getSelectionProvider().getSelection()).getOffset() - 1, 0);
256279
Control styledText = editor.getAdapter(Control.class);
257280
Event e = new Event();
258281
e.type = ST.VerifyKey;
@@ -284,6 +307,11 @@ public void closeShell() {
284307
}
285308
}
286309

310+
@AfterEach
311+
public void stopLongRunningCompletionProposalProcessor() {
312+
LongRunningBarContentAssistProcessor.finish();
313+
}
314+
287315
private static final class TestLogListener implements ILogListener {
288316

289317
List<IStatus> messages= new ArrayList<>();
@@ -301,8 +329,8 @@ private static final class MockContentAssistProcessor implements IContentAssistP
301329

302330
@Override
303331
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
304-
called= true;
305-
return null;
332+
this.called = true;
333+
return new ICompletionProposal[0];
306334
}
307335

308336
@Override
@@ -329,6 +357,5 @@ public String getErrorMessage() {
329357
public IContextInformationValidator getContextInformationValidator() {
330358
return null;
331359
}
332-
333360
}
334361
}

tests/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/TestQuickAssist.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static org.eclipse.ui.tests.harness.util.DisplayHelper.runEventLoop;
1818
import static org.eclipse.ui.tests.harness.util.DisplayHelper.waitForCondition;
1919
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertNotNull;
2021
import static org.junit.jupiter.api.Assertions.assertTrue;
2122

2223
import java.util.Arrays;
@@ -102,21 +103,22 @@ private Shell openQuickAssist() {
102103
editor.selectAndReveal(3, 0);
103104
TextOperationAction action = (TextOperationAction) editor.getAction(ITextEditorActionConstants.QUICK_ASSIST);
104105
action.update();
105-
final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells()).filter(Shell::isVisible).collect(Collectors.toSet());
106+
final Set<Shell> beforeShells = Arrays.stream(editor.getSite().getShell().getDisplay().getShells())
107+
.filter(Shell::isVisible).collect(Collectors.toSet());
106108
action.run();
107-
Shell shell= CompletionTest.findNewShell(beforeShells, editor.getSite().getShell().getDisplay(), true);
108-
runEventLoop(PlatformUI.getWorkbench().getDisplay(),100);
109+
Shell shell= CompletionTest.findNewShell(beforeShells, editor.getSite().getShell().getDisplay());
110+
assertNotNull(shell, "Shell is expected to open for quick assist");
111+
runEventLoop(PlatformUI.getWorkbench().getDisplay(), 100);
109112
return shell;
110113
}
111114

112115
/**
113116
* Checks that a mock quick assist proposal comes up
114-
*
117+
*
115118
* @param completionProposalList the quick assist proposal list
116119
* @param proposals expected proposals
117120
*/
118121
private void checkCompletionContent(final Table completionProposalList, String[] proposals) {
119-
// should be instantaneous, but happens to go asynchronous on CI so let's allow a wait
120122
waitForCondition(completionProposalList.getDisplay(), 200,
121123
() -> completionProposalList.getItemCount() >= proposals.length);
122124
assertEquals(proposals.length, completionProposalList.getItemCount());
@@ -126,7 +128,6 @@ private void checkCompletionContent(final Table completionProposalList, String[]
126128
}
127129
}
128130

129-
130131
@AfterEach
131132
public void closeShell() {
132133
if (this.completionShell != null && !completionShell.isDisposed()) {

tests/org.eclipse.ui.genericeditor.tests/src/org/eclipse/ui/genericeditor/tests/contributions/LongRunningBarContentAssistProcessor.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@
1313
*******************************************************************************/
1414
package org.eclipse.ui.genericeditor.tests.contributions;
1515

16+
import java.util.concurrent.atomic.AtomicBoolean;
17+
1618
import org.eclipse.jface.text.ITextViewer;
1719
import org.eclipse.jface.text.contentassist.ICompletionProposal;
1820

1921
public class LongRunningBarContentAssistProcessor extends BarContentAssistProcessor {
2022

2123
public static final String LONG_RUNNING_BAR_CONTENT_ASSIST_PROPOSAL = "bars are also good for soft drink cocktails.";
22-
public static final int DELAY = 2000;
24+
public static final int TIMEOUT_MSEC = 10_000;
25+
private static final AtomicBoolean running = new AtomicBoolean();
2326

2427
public LongRunningBarContentAssistProcessor() {
2528
super(LONG_RUNNING_BAR_CONTENT_ASSIST_PROPOSAL);
@@ -28,11 +31,22 @@ public LongRunningBarContentAssistProcessor() {
2831
@Override
2932
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
3033
try {
31-
Thread.sleep(DELAY);
34+
long startExecutionTime = System.currentTimeMillis();
35+
while (running.get() && (System.currentTimeMillis() - startExecutionTime) < TIMEOUT_MSEC) {
36+
Thread.sleep(20);
37+
}
3238
} catch (InterruptedException e) {
33-
// TODO Auto-generated catch block
34-
e.printStackTrace();
39+
// Just finish on unexpected interrupt
3540
}
3641
return super.computeCompletionProposals(viewer, offset);
3742
}
43+
44+
public static void enable() {
45+
running.set(true);
46+
}
47+
48+
public static void finish() {
49+
running.set(false);
50+
}
51+
3852
}

0 commit comments

Comments
 (0)