Skip to content

Commit 58fb773

Browse files
committed
Text control should not redraw indefinitely
In some scenarios, `Text` control redraws on every event loop iteration. See eclipse-platform/eclipse.platform.ui#3920 This is a JUnit test reproducing the problem.
1 parent f03d65d commit 58fb773

2 files changed

Lines changed: 115 additions & 1 deletion

File tree

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/SwtTestUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public class SwtTestUtil {
8383

8484
// used to specify verbose mode, if true unimplemented warning messages will
8585
// be written to System.out
86-
public static boolean verbose = false;
86+
public static boolean verbose = true;
8787

8888
// allow specific image formats to be tested
8989
public static String[] imageFormats = new String[] {"bmp", "jpg", "gif", "png"};

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Text.java

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@
1313
*******************************************************************************/
1414
package org.eclipse.swt.tests.junit;
1515

16+
import static java.lang.System.currentTimeMillis;
17+
import static java.lang.System.nanoTime;
1618
import static org.eclipse.swt.tests.junit.SwtTestUtil.JENKINS_DETECT_ENV_VAR;
1719
import static org.eclipse.swt.tests.junit.SwtTestUtil.JENKINS_DETECT_REGEX;
1820
import static org.junit.jupiter.api.Assertions.assertEquals;
1921
import static org.junit.jupiter.api.Assertions.assertFalse;
2022
import static org.junit.jupiter.api.Assertions.assertThrows;
2123
import static org.junit.jupiter.api.Assertions.assertTrue;
24+
import static org.junit.jupiter.api.Assertions.fail;
2225

2326
import org.eclipse.swt.SWT;
2427
import org.eclipse.swt.events.ModifyListener;
@@ -30,6 +33,7 @@
3033
import org.eclipse.swt.graphics.Font;
3134
import org.eclipse.swt.graphics.FontData;
3235
import org.eclipse.swt.graphics.Point;
36+
import org.eclipse.swt.layout.FillLayout;
3337
import org.eclipse.swt.widgets.Display;
3438
import org.eclipse.swt.widgets.Event;
3539
import org.eclipse.swt.widgets.Group;
@@ -1376,6 +1380,58 @@ public void test_showSelection() {
13761380
text.showSelection();
13771381
}
13781382

1383+
// Originally reported as https://github.com/eclipse-platform/eclipse.platform.ui/issues/3920
1384+
@Test
1385+
public void test_finiteRedrawCancelButtonWithBackground() {
1386+
if ( text != null ) text.dispose();
1387+
// Style constants are causing
1388+
// org.eclipse.swt.widgets.Text.drawInteriorWithFrame_inView_searchfield(long, long, NSRect, long)
1389+
// to call
1390+
// org.eclipse.swt.internal.cocoa.NSControl.stringValue()
1391+
// which schedules redraw
1392+
text = new Text(shell, SWT.SEARCH | SWT.ICON_CANCEL);
1393+
// Background prevents early exit from drawInteriorWithFrame_inView_searchfield(long, long, NSRect, long)
1394+
text.setBackground(shell.getDisplay().getSystemColor(SWT.COLOR_RED));
1395+
setWidget(text);
1396+
shell.setLayout(new FillLayout());
1397+
text.requestLayout();
1398+
shell.open();
1399+
text.forceFocus();
1400+
waitUntilIdle();
1401+
assertIdle();
1402+
}
1403+
1404+
@Test
1405+
public void test_finiteRedrawCancelButton() {
1406+
if ( text != null ) text.dispose();
1407+
// Style constants are causing
1408+
// org.eclipse.swt.widgets.Text.drawInteriorWithFrame_inView_searchfield(long, long, NSRect, long)
1409+
// to call
1410+
// org.eclipse.swt.internal.cocoa.NSControl.stringValue()
1411+
// which schedules redraw
1412+
text = new Text(shell, SWT.SEARCH | SWT.ICON_CANCEL);
1413+
setWidget(text);
1414+
shell.setLayout(new FillLayout());
1415+
text.requestLayout();
1416+
shell.open();
1417+
text.forceFocus();
1418+
waitUntilIdle();
1419+
assertIdle();
1420+
}
1421+
1422+
@Test
1423+
public void test_finiteRedraw() {
1424+
if ( text != null ) text.dispose();
1425+
text = new Text(shell, SWT.NONE);
1426+
setWidget(text);
1427+
shell.setLayout(new FillLayout());
1428+
text.requestLayout();
1429+
shell.open();
1430+
text.forceFocus();
1431+
waitUntilIdle();
1432+
assertIdle();
1433+
}
1434+
13791435
/* custom */
13801436
Text text;
13811437
String delimiterString;
@@ -1639,4 +1695,62 @@ private void pasteFromClipboard(Text text) throws InterruptedException {
16391695
SwtTestUtil.processEvents(1000, () -> !oldText.equals(text.getText()));
16401696
}
16411697

1698+
1699+
private void drainEventsWithTimeout() {
1700+
long hangTimeout = currentTimeMillis() + 1000;
1701+
while (currentTimeMillis() < hangTimeout) {
1702+
if (!shell.getDisplay().readAndDispatch()) {
1703+
return;
1704+
}
1705+
}
1706+
fail("UI scheduler should settle");
1707+
}
1708+
1709+
private void waitUntilIdle() {
1710+
long hangTimeout = currentTimeMillis() + 1000;
1711+
long lastActive = nanoTime();
1712+
while (currentTimeMillis() < hangTimeout) {
1713+
if (shell.getDisplay().readAndDispatch()) {
1714+
lastActive = currentTimeMillis();
1715+
} else {
1716+
if (lastActive < nanoTime() - 10_000_000) {
1717+
return;
1718+
}
1719+
Thread.yield();
1720+
}
1721+
}
1722+
fail("Unexpected system events keep coming");
1723+
}
1724+
1725+
private void assertIdle() {
1726+
long start = currentTimeMillis();
1727+
long stop = start + 1000;
1728+
int eventStreakCount = 0;
1729+
int idleIterations = 0;
1730+
while (currentTimeMillis() < stop) {
1731+
idleIterations++;
1732+
// If redraws are constantly scheduled, readAndDispatch() rarely return false.
1733+
// Side effects - high CPU usage, asyncExec() stops working in modal contexts
1734+
if (shell.getDisplay().readAndDispatch()) {
1735+
eventStreakCount++;
1736+
// Skip other events of the streak
1737+
// No need to count events individually, as many events immediately following each other are common and normal
1738+
// Currently, streaks are short, but we do not want any noise to cause test failures as UI evolves
1739+
drainEventsWithTimeout();
1740+
} else {
1741+
Thread.yield();
1742+
}
1743+
}
1744+
assertTrue(idleIterations > 1000, "Some idle event loop iterations are expected when UI is doing nothing");
1745+
double intensity = ((double)eventStreakCount/idleIterations);
1746+
String message = "Detected %d/%d UI event streaks/idle event loop iterations per second, intensity %.2g. Expected: 2/100_000.";
1747+
1748+
message = message.formatted(eventStreakCount, idleIterations, intensity);
1749+
assertTrue(eventStreakCount < 50, message);
1750+
assertTrue(intensity < 1e-4, message);
1751+
if (SwtTestUtil.verbose) {
1752+
System.out.println(message);
1753+
}
1754+
}
1755+
16421756
}

0 commit comments

Comments
 (0)