Skip to content

Commit b9920a0

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 b9920a0

2 files changed

Lines changed: 114 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: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
*******************************************************************************/
1414
package org.eclipse.swt.tests.junit;
1515

16+
import static java.lang.System.currentTimeMillis;
1617
import static org.eclipse.swt.tests.junit.SwtTestUtil.JENKINS_DETECT_ENV_VAR;
1718
import static org.eclipse.swt.tests.junit.SwtTestUtil.JENKINS_DETECT_REGEX;
1819
import static org.junit.jupiter.api.Assertions.assertEquals;
1920
import static org.junit.jupiter.api.Assertions.assertFalse;
2021
import static org.junit.jupiter.api.Assertions.assertThrows;
2122
import static org.junit.jupiter.api.Assertions.assertTrue;
23+
import static org.junit.jupiter.api.Assertions.fail;
2224

2325
import org.eclipse.swt.SWT;
2426
import org.eclipse.swt.events.ModifyListener;
@@ -30,6 +32,7 @@
3032
import org.eclipse.swt.graphics.Font;
3133
import org.eclipse.swt.graphics.FontData;
3234
import org.eclipse.swt.graphics.Point;
35+
import org.eclipse.swt.layout.FillLayout;
3336
import org.eclipse.swt.widgets.Display;
3437
import org.eclipse.swt.widgets.Event;
3538
import org.eclipse.swt.widgets.Group;
@@ -1376,6 +1379,58 @@ public void test_showSelection() {
13761379
text.showSelection();
13771380
}
13781381

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

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

0 commit comments

Comments
 (0)