Skip to content

Commit fc2497e

Browse files
HeikoKlareCopilot
andcommitted
Add Win32-specific tests for Cursor
CursorWin32Tests covers behaviour that is either Windows-only or directly exercises the internal implementation paths changed in the preceding cleanup commit: - testHandleIsNonZeroForValidCursor: baseline sanity check that win32_getHandle() returns a non-zero OS handle for a live cursor. - testDisposedCursorReturnsZeroHandle: verifies the isDisposed() guard at the top of win32_getHandle() returns 0L after disposal. - testHandleIsCachedForSameZoomLevel: verifies the computeIfAbsent() caching - two calls with the same zoom must return the identical cached OS handle without re-creating it. - testStyleCursorsWithSameStyleAreEqual: Windows-specific - LoadCursor() returns a shared system handle for the same cursor ID, so two independently created style cursors must satisfy equals(). This is a regression test for the previous Long-reference == comparison that could have silently returned false for handles outside the JVM integer cache range. - testImageDataCursorProducesDifferentHandlesForDifferentZoomLevels: verifies that ImageDataCursorHandleProvider scales the source image and produces a distinct OS cursor handle for each zoom level. - testDestroyHandlesExceptPreservesRetainedHandle: verifies that destroyHandlesExcept() leaves the retained zoom entry intact and does not mark the cursor as disposed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent bb08a73 commit fc2497e

File tree

1 file changed

+131
-0
lines changed

1 file changed

+131
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*******************************************************************************/
11+
package org.eclipse.swt.graphics;
12+
13+
import static org.junit.jupiter.api.Assertions.*;
14+
15+
import java.util.*;
16+
17+
import org.eclipse.swt.*;
18+
import org.eclipse.swt.internal.*;
19+
import org.eclipse.swt.widgets.*;
20+
import org.junit.jupiter.api.*;
21+
import org.junit.jupiter.api.extension.*;
22+
23+
@ExtendWith(PlatformSpecificExecutionExtension.class)
24+
@ExtendWith(WithMonitorSpecificScalingExtension.class)
25+
class CursorWin32Tests {
26+
27+
private Display display;
28+
29+
@BeforeEach
30+
void setUp() {
31+
display = Display.getDefault();
32+
}
33+
34+
@Test
35+
void testHandleIsNonZeroForValidCursor() {
36+
Cursor cursor = new Cursor(display, SWT.CURSOR_ARROW);
37+
try {
38+
assertNotEquals(0L, Cursor.win32_getHandle(cursor, 100),
39+
"A non-disposed cursor must return a non-zero OS handle");
40+
} finally {
41+
cursor.dispose();
42+
}
43+
}
44+
45+
@Test
46+
void testDisposedCursorReturnsZeroHandle() {
47+
Cursor cursor = new Cursor(display, SWT.CURSOR_ARROW);
48+
cursor.dispose();
49+
assertEquals(0L, Cursor.win32_getHandle(cursor, 100),
50+
"A disposed cursor must return a zero handle");
51+
}
52+
53+
@Test
54+
void testHandleIsCachedForSameZoomLevel() {
55+
Cursor cursor = new Cursor(display, SWT.CURSOR_ARROW);
56+
try {
57+
long first = Cursor.win32_getHandle(cursor, 100);
58+
long second = Cursor.win32_getHandle(cursor, 100);
59+
assertEquals(first, second,
60+
"Repeated calls with the same zoom must return the cached handle");
61+
} finally {
62+
cursor.dispose();
63+
}
64+
}
65+
66+
/**
67+
* On Windows, LoadCursor() returns the same shared system handle for the same
68+
* cursor style, so two independently created style-based Cursor objects must be
69+
* considered equal. This is the Windows-specific behaviour noted in
70+
* Test_org_eclipse_swt_graphics_Cursor and exercises the equals/hashCode path
71+
* that previously compared boxed Long references with ==.
72+
*/
73+
@Test
74+
void testStyleCursorsWithSameStyleAreEqual() {
75+
Cursor cursor1 = new Cursor(display, SWT.CURSOR_WAIT);
76+
Cursor cursor2 = new Cursor(display, SWT.CURSOR_WAIT);
77+
try {
78+
assertTrue(cursor1.equals(cursor2),
79+
"On Windows, two cursors with the same style must be equal because LoadCursor reuses the shared OS handle");
80+
assertEquals(cursor1.hashCode(), cursor2.hashCode(),
81+
"Equal cursors must have the same hash code");
82+
} finally {
83+
cursor1.dispose();
84+
cursor2.dispose();
85+
}
86+
}
87+
88+
@Test
89+
void testImageDataCursorProducesDifferentHandlesForDifferentZoomLevels() {
90+
// 32bpp image with uniform alpha — takes the ARGB path in setupCursorFromImageData
91+
ImageData source = new ImageData(16, 16, 32,
92+
new PaletteData(0xFF00, 0xFF0000, 0xFF000000));
93+
source.alpha = 255;
94+
95+
Cursor cursor = new Cursor(display, source, 0, 0);
96+
try {
97+
long handle100 = Cursor.win32_getHandle(cursor, 100);
98+
long handle200 = Cursor.win32_getHandle(cursor, 200);
99+
100+
assertNotEquals(0L, handle100, "Handle at 100% zoom must be non-zero");
101+
assertNotEquals(0L, handle200, "Handle at 200% zoom must be non-zero");
102+
assertNotEquals(handle100, handle200,
103+
"Different zoom levels must produce distinct OS cursor handles (different physical sizes)");
104+
} finally {
105+
cursor.dispose();
106+
}
107+
}
108+
109+
@Test
110+
void testDestroyHandlesExceptPreservesRetainedHandle() {
111+
// 32bpp ARGB source so we get a unique, non-shared OS handle per zoom level
112+
ImageData source = new ImageData(16, 16, 32,
113+
new PaletteData(0xFF00, 0xFF0000, 0xFF000000));
114+
source.alpha = 255;
115+
116+
Cursor cursor = new Cursor(display, source, 0, 0);
117+
try {
118+
long handle100 = Cursor.win32_getHandle(cursor, 100);
119+
Cursor.win32_getHandle(cursor, 200); // populate a second zoom level
120+
121+
cursor.destroyHandlesExcept(Set.of(DPIUtil.getZoomForAutoscaleProperty(100)));
122+
123+
// The cursor itself must still be alive and the retained handle unchanged
124+
assertFalse(cursor.isDisposed(), "Cursor must not be disposed after destroyHandlesExcept");
125+
assertEquals(handle100, Cursor.win32_getHandle(cursor, 100),
126+
"The handle for the retained zoom level must be unchanged after destroyHandlesExcept");
127+
} finally {
128+
cursor.dispose();
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)