Signed-off-by: Jinoh Kang jinoh.kang.kr@gmail.com
-- v6: user32/tests: Test for window exposure behaviours.
From: Jinoh Kang jinoh.kang.kr@gmail.com
--- dlls/user32/tests/msg.c | 390 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+)
diff --git a/dlls/user32/tests/msg.c b/dlls/user32/tests/msg.c index 9c6f0943950..6a600d45c2a 100644 --- a/dlls/user32/tests/msg.c +++ b/dlls/user32/tests/msg.c @@ -9044,6 +9044,390 @@ static void test_paint_messages(void) DeleteObject( hrgn2 ); }
+static void visualize_region_differences( HWND hwnd, HWND hother, HRGN hrgn_expect, HRGN hrgn_actual ) +{ + HBRUSH b_expectonly, b_actualonly, b_intersect; + HRGN hrgn_intersect; + HWND hstatic, hshow, hhide; + HDC hdc, hdctmp; + HBITMAP hbitmap; + MSG msg; + RECT rect; + DWORD start_time, elapsed, timeout = 60 * 1000; + BOOL toggle = TRUE, stop = FALSE; + + start_time = GetTickCount(); + + b_expectonly = CreateSolidBrush( RGB( 64, 64, 255 )); + b_actualonly = CreateSolidBrush( RGB( 255, 64, 64 )); + b_intersect = CreateSolidBrush( RGB( 159, 64, 159 )); + + hrgn_intersect = CreateRectRgn( 0, 0, 0, 0 ); + CombineRgn( hrgn_intersect, hrgn_expect, hrgn_actual, RGN_AND ); + + GetClientRect( hwnd, &rect ); + hdc = GetDC( hwnd ); + hbitmap = CreateCompatibleBitmap( hdc, rect.right, rect.bottom ); + hdctmp = CreateCompatibleDC( hdc ); + ReleaseDC( hwnd, hdc ); + + SelectObject( hdctmp, hbitmap ); + FillRgn( hdctmp, hrgn_expect, b_expectonly ); + FillRgn( hdctmp, hrgn_actual, b_actualonly ); + FillRgn( hdctmp, hrgn_intersect, b_intersect ); + + DeleteObject( hdctmp ); + DeleteObject( hrgn_intersect ); + DeleteObject( b_intersect ); + DeleteObject( b_actualonly ); + DeleteObject( b_expectonly ); + + hstatic = CreateWindowExA( 0, WC_STATICA, "", WS_CHILD | SS_BITMAP, + 0, 0, rect.right, rect.bottom, hwnd, 0, 0, NULL ); + SendMessageA( hstatic, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbitmap ); + + hshow = hstatic; + hhide = hother; + + for (;;) + { + if (stop) toggle = hshow == hother; + if (toggle) + { + HWND htmp; + HDWP hdwp; + + hdwp = BeginDeferWindowPos( !!hhide + !!hshow ); + if (hhide) + { + DeferWindowPos( hdwp, hhide, NULL, 0, 0, 0, 0, + SWP_HIDEWINDOW | SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER ); + } + if (hshow) + { + DeferWindowPos( hdwp, hshow, HWND_TOP, 0, 0, 0, 0, + SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE ); + } + EndDeferWindowPos( hdwp ); + + htmp = hshow; + hshow = hhide; + hhide = htmp; + toggle = FALSE; + } + if (stop) break; + if ((elapsed = GetTickCount() - start_time) >= timeout) + { + stop = TRUE; + continue; + } + MsgWaitForMultipleObjects( 0, NULL, FALSE, timeout - elapsed, QS_ALLINPUT ); + while (PeekMessageA( &msg, 0, 0, 0, PM_REMOVE )) + { + TranslateMessage( &msg ); + DispatchMessageA( &msg ); + if (msg.message == WM_MOUSEMOVE) + { + start_time = GetTickCount(); + } + else if (msg.message == WM_LBUTTONUP || (msg.message == WM_CHAR && msg.wParam == VK_SPACE)) + { + toggle = !toggle; + } + else if (msg.message == WM_RBUTTONUP || (msg.message == WM_CHAR && msg.wParam == VK_RETURN)) + { + stop = TRUE; + } + } + } + + DestroyWindow( hstatic ); + DeleteObject( hbitmap ); +} + +#define subtest_swp_paint_regions(w,p,c) subtest_swp_paint_regions_(__LINE__,w,p,c) + +static void subtest_swp_paint_regions_( int line, int wrap_toplevel, LPCSTR parent_class, LPCSTR child_class ) +{ + static const struct exposure_test { + int ex_style, style; + BOOL shuffle_zorder; + HWND insert_after; + } exposure_tests[] = { + { 0, WS_CLIPCHILDREN, FALSE, NULL }, + { 0, 0, FALSE, NULL }, + { WS_EX_COMPOSITED, WS_CLIPCHILDREN, FALSE, NULL }, + { WS_EX_COMPOSITED, WS_CLIPCHILDREN, TRUE , HWND_TOP }, + { WS_EX_COMPOSITED, WS_CLIPCHILDREN, TRUE , HWND_BOTTOM }, + { WS_EX_COMPOSITED, 0, FALSE, NULL }, + { WS_EX_COMPOSITED, 0, TRUE , HWND_TOP }, + { WS_EX_COMPOSITED, 0, TRUE , HWND_BOTTOM }, + }; + size_t i; + HWND htoplevel = NULL, hparent, hchild, hauxchild; + const RECT rect_old = { 10, 10, 100, 100 }; + HRGN hrgn_old_vis = CreateRectRgn( 0, 0, 0, 0 ); + HRGN hrgn_new_vis = CreateRectRgn( 0, 0, 0, 0 ); + HRGN hrgn_expect = CreateRectRgn( 0, 0, 0, 0 ); + HRGN hrgn_actual = CreateRectRgn( 0, 0, 0, 0 ); + HRGN hrgn_old_vis_child = CreateRectRgn( 0, 0, 0, 0 ); + HRGN hrgn_new_vis_child = CreateRectRgn( 0, 0, 0, 0 ); + HRGN hrgn_expect_child = CreateRectRgn( 0, 0, 0, 0 ); + HRGN hrgn_actual_child = CreateRectRgn( 0, 0, 0, 0 ); + int base_style; + BOOL is_composition_possible, has_parentdc_anomaly; + WNDCLASSA parent_wc; + + if (wrap_toplevel) + { + htoplevel = CreateWindowExA( 0, "SimpleWindowClass", "Test toplevel", WS_OVERLAPPEDWINDOW | WS_VISIBLE, + 100, 100, 400, 400, 0, 0, 0, NULL ); + ok( htoplevel != 0, "Failed to create top-level window: %lu\n", GetLastError() ); + base_style = WS_CHILD | WS_VISIBLE; + } + else + { + base_style = WS_OVERLAPPEDWINDOW | WS_VISIBLE; + } + + ok( GetClassInfoA( GetModuleHandleA( NULL ), parent_class, &parent_wc ), + "GetClassInfoA failed\n" ); + + is_composition_possible = (base_style & (WS_POPUP|WS_CHILD)) != WS_CHILD || + (parent_wc.style & CS_PARENTDC) == 0; + + has_parentdc_anomaly = (base_style & (WS_POPUP|WS_CHILD)) != WS_CHILD && + (parent_wc.style & CS_PARENTDC) != 0; + + hparent = CreateWindowExA( 0, parent_class, "Test parent", base_style, + 80, 80, 200, 200, htoplevel, 0, 0, NULL ); + ok( hparent != 0, "Creating parent window (%s) returned error %lu\n", + debugstr_a( parent_class ), GetLastError() ); + + hchild = CreateWindowExA( 0, child_class, "Test child", WS_CHILD | WS_VISIBLE | WS_BORDER, + rect_old.left, rect_old.top, + rect_old.right - rect_old.left, rect_old.bottom - rect_old.top, + hparent, 0, 0, NULL ); + ok( hchild != 0, "Creating child window (%s) returned error %lu\n", + debugstr_a( child_class ), GetLastError() ); + + hauxchild = CreateWindowExA( 0, child_class, "Auxiliary child for z order test", WS_CHILD | WS_VISIBLE, + 110, 0, 0, 0, hparent, 0, 0, NULL ); + ok( hauxchild != 0, "Creating child window (%s) returned error %lu\n", + debugstr_a( child_class ), GetLastError() ); + + for (i = 0; i < ARRAY_SIZE(exposure_tests); i++) + { + const struct exposure_test *extest = &exposure_tests[i]; + BOOL has_ws_ex_composited = (extest->ex_style & WS_EX_COMPOSITED) != 0; + BOOL is_composited = is_composition_possible && has_ws_ex_composited; + BOOL is_zorder_redraw = is_composited && extest->shuffle_zorder; + int delta; + + winetest_push_context( "%d: SetWindowPos redraw #%Id (ex_style = %#x, style = %#x, shuffle_zorder = %d, insert_after = %Id)", + line, i, extest->ex_style, extest->style, extest->shuffle_zorder, (LONG_PTR)extest->insert_after ); + + SetWindowLongA( hparent, GWL_EXSTYLE, extest->ex_style ); + SetWindowLongA( hparent, GWL_STYLE, base_style | extest->style ); + RedrawWindow( hparent, NULL, NULL, RDW_INVALIDATE|RDW_ERASE|RDW_FRAME ); + + for (delta = -20; delta <= 20; delta += 20) + { + RECT rect_old_vis, rect_new, rect_new_vis; + RECT rect_parent_clip, rect_child_clip; + RECT rect_old_vis_child, rect_new_vis_child; + BOOL rgn_ok; + + winetest_push_context( "delta = %+d", delta ); + + SetWindowPos( hchild, HWND_TOP, + rect_old.left, + rect_old.top, + rect_old.right - rect_old.left, + rect_old.bottom - rect_old.top, + SWP_NOACTIVATE ); + + rect_new = rect_old; + OffsetRect( &rect_new, delta, delta ); + + rect_old_vis_child = rect_old; + MapWindowPoints( hparent, hchild, (POINT *)&rect_old_vis_child, 2 ); + + SetRectRgn( hrgn_actual, 0, 0, 0, 0 ); + SetRectRgn( hrgn_actual_child, 0, 0, 0, 0 ); + + UpdateWindow( hparent ); + flush_events(); + + if (extest->shuffle_zorder) + { + /* bring sibling to top/bottom first so we can trigger z-order change */ + SetWindowPos( hauxchild, extest->insert_after, 0, 0, 0, 0, + SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE ); + } + + SetWindowPos( hchild, extest->insert_after, + rect_new.left, + rect_new.top, + rect_new.right - rect_new.left, + rect_new.bottom - rect_new.top, + SWP_NOACTIVATE | + (extest->shuffle_zorder || extest->insert_after ? 0 : SWP_NOZORDER) ); + + ok( GetUpdateRgn( hparent, hrgn_actual, FALSE ) != ERROR, + "GetUpdateRgn on parentshall succeed\n" ); + ok( GetUpdateRgn( hchild, hrgn_actual_child, FALSE ) != ERROR, + "GetUpdateRgn on child shall succeed\n" ); + + /* Compute parent window expose region */ + GetClientRect( hparent, &rect_parent_clip ); + IntersectRect( &rect_old_vis, &rect_old, &rect_parent_clip ); + SetRectRgn( hrgn_old_vis, rect_old_vis.left, rect_old_vis.top, rect_old_vis.right, rect_old_vis.bottom ); + IntersectRect( &rect_new_vis, &rect_new, &rect_parent_clip ); + SetRectRgn( hrgn_new_vis, rect_new_vis.left, rect_new_vis.top, rect_new_vis.right, rect_new_vis.bottom ); + + if (!EqualRect( &rect_old, &rect_new ) || is_zorder_redraw) + { + CombineRgn( hrgn_expect, hrgn_old_vis, hrgn_new_vis, is_composited ? RGN_OR : RGN_DIFF ); + } + else + { + SetRectRgn( hrgn_expect, 0, 0, 0, 0 ); + } + + rgn_ok = EqualRgn( hrgn_expect, hrgn_actual ); + if (!rgn_ok && broken( has_parentdc_anomaly && is_composited /* Win7 */ )) + { + if (winetest_debug > 1) + { + trace( "Forcing non-composited update region (broken)\n" ); + } + rgn_ok = 1; + } + else + { + todo_wine_if( EqualRect( &rect_old, &rect_new ) ? is_zorder_redraw : + ((extest->style & WS_CLIPCHILDREN) == 0 || is_composited) ) + ok( !!rgn_ok, "Parent update region shall match expected region\n" ); + } + + if (!rgn_ok && winetest_debug > 1) + { + trace( "Expected parent update region: " ); + dump_region( hrgn_expect ); + trace( "Actual parent update region: " ); + dump_region( hrgn_actual ); + trace( "Old child window visible area: %s\n", wine_dbgstr_rect( &rect_old_vis ) ); + trace( "New child window visible area: %s\n", wine_dbgstr_rect( &rect_new_vis ) ); + } + + if (winetest_interactive) + { + if (!rgn_ok) + { + visualize_region_differences( hparent, hchild, hrgn_expect, hrgn_actual ); + } + + /* Let the position change be visible to the user */ + flush_events(); + } + + rect_new_vis_child = rect_new; + MapWindowPoints( hparent, hchild, (POINT *)&rect_new_vis_child, 2 ); + + /* Compute child window expose region */ + GetClientRect( hchild, &rect_child_clip ); + if (is_composited) + { + RECT rect_outer_clip; + GetClientRect( hparent, &rect_outer_clip ); + MapWindowPoints( hparent, hchild, (POINT *)&rect_outer_clip, 2 ); + IntersectRect( &rect_child_clip, &rect_child_clip, &rect_outer_clip ); + } + IntersectRect( &rect_old_vis_child, &rect_old_vis_child, &rect_child_clip ); + SetRectRgn( hrgn_old_vis_child, rect_old_vis_child.left, rect_old_vis_child.top, rect_old_vis_child.right, rect_old_vis_child.bottom ); + IntersectRect( &rect_new_vis_child, &rect_new_vis_child, &rect_child_clip ); + SetRectRgn( hrgn_new_vis_child, rect_new_vis_child.left, rect_new_vis_child.top, rect_new_vis_child.right, rect_new_vis_child.bottom ); + + if (!EqualRect( &rect_old, &rect_new ) || is_zorder_redraw) + { + CombineRgn( hrgn_expect_child, hrgn_new_vis_child, hrgn_old_vis_child, is_composited ? RGN_OR : RGN_DIFF ); + } + else + { + SetRectRgn( hrgn_expect_child, 0, 0, 0, 0 ); + } + + rgn_ok = EqualRgn( hrgn_expect_child, hrgn_actual_child ); + if (!rgn_ok && broken( has_parentdc_anomaly && is_composited /* Win7 */ )) + { + if (winetest_debug > 1) + { + trace( "Forcing non-composited update region (broken)\n" ); + } + rgn_ok = 1; + } + else + { + todo_wine_if( EqualRect( &rect_old, &rect_new ) ? is_zorder_redraw : + ((extest->style & WS_CLIPCHILDREN) == 0 || is_composited) ) + ok( !!rgn_ok, "Child update region shall match expected region\n" ); + } + + if (!rgn_ok && winetest_debug > 1) + { + trace( "Expected child update region: " ); + dump_region( hrgn_expect_child ); + trace( "Actual child update region: " ); + dump_region( hrgn_actual_child ); + trace( "Old child window client visible area: %s\n", wine_dbgstr_rect( &rect_old_vis_child ) ); + trace( "New child window client visible area: %s\n", wine_dbgstr_rect( &rect_new_vis_child ) ); + } + + if (winetest_interactive) + { + if (!rgn_ok) + { + visualize_region_differences( hchild, NULL, hrgn_expect_child, hrgn_actual_child ); + } + + /* Let the position change be visible to the user */ + flush_events(); + } + + winetest_pop_context(); + } + + winetest_pop_context(); + } + + DestroyWindow( hauxchild ); + DestroyWindow( hchild ); + DestroyWindow( hparent ); + if (htoplevel) DestroyWindow( htoplevel ); + + DeleteObject( hrgn_actual_child ); + DeleteObject( hrgn_expect_child ); + DeleteObject( hrgn_new_vis_child ); + DeleteObject( hrgn_old_vis_child ); + DeleteObject( hrgn_actual ); + DeleteObject( hrgn_expect ); + DeleteObject( hrgn_new_vis ); + DeleteObject( hrgn_old_vis ); +} + +static void test_swp_paint_regions(void) +{ + subtest_swp_paint_regions( 1, "SimpleWindowClass", "SimpleWindowClass" ); + subtest_swp_paint_regions( 1, "SimpleWindowClass", "SimpleWindowClassWithParentDC" ); + subtest_swp_paint_regions( 1, "SimpleWindowClassWithParentDC", "SimpleWindowClass" ); + subtest_swp_paint_regions( 1, "SimpleWindowClassWithParentDC", "SimpleWindowClassWithParentDC" ); + subtest_swp_paint_regions( 0, "SimpleWindowClass", "SimpleWindowClass" ); + subtest_swp_paint_regions( 0, "SimpleWindowClass", "SimpleWindowClassWithParentDC" ); + subtest_swp_paint_regions( 0, "SimpleWindowClassWithParentDC", "SimpleWindowClass" ); + subtest_swp_paint_regions( 0, "SimpleWindowClassWithParentDC", "SimpleWindowClassWithParentDC" ); +} + struct wnd_event { HWND hwnd; @@ -10389,6 +10773,11 @@ static BOOL RegisterWindowClasses(void) cls.lpszClassName = "TestDialogClass"; if(!RegisterClassA(&cls)) return FALSE;
+ cls.lpfnWndProc = DefWindowProcA; + cls.style = CS_PARENTDC; + cls.lpszClassName = "SimpleWindowClassWithParentDC"; + if(!RegisterClassA(&cls)) return FALSE; + clsW.style = 0; clsW.lpfnWndProc = MsgCheckProcW; clsW.cbClsExtra = 0; @@ -19111,6 +19500,7 @@ START_TEST(msg) test_combobox_messages(); test_wmime_keydown_message(); test_paint_messages(); + test_swp_paint_regions(); test_interthread_messages(); test_message_conversion(); test_accelerators();
Hi,
It looks like your patch introduced the new failures shown below. Please investigate and fix them before resubmitting your patch. If they are not new, fixing them anyway would help a lot. Otherwise please ask for the known failures list to be updated.
The tests also ran into some preexisting test failures. If you know how to fix them that would be helpful. See the TestBot job for the details:
The full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=125158
Your paranoid android.
=== w10pro64 (32 bit report) ===
user32: msg.c:13452: Test failed: coords not changed: (101 101) (101 101) msg.c:13470: Test failed: coords not changed: (101 101) (101 101)
On Wed Oct 19 15:42:13 2022 +0000, Jinoh Kang wrote:
changed this line in [version 6 of the diff](/wine/wine/-/merge_requests/363/diffs?diff_id=14545&start_sha=bb1f807eb4241db1e7ed207d0f603ad6e2d02649#f8a9b0a0de0f400890a130a2270e9b8ec63f701d_9363_9362)
Oops, forgot actually changing the condition. My apologies.
On Wed Oct 19 12:35:58 2022 +0000, Jacek Caban wrote:
You never clean up those regions.
Only the child ones, right? Thanks!
Is it so bad?
Yes. Notice the Marvin complaints above.
Maybe we could cover multiple skips with a single win_skip() or something like that. We could also maybe limit number of tested cases, some cases are surely less interesting than others.
This is a split-out of PR !231, and they are necessary to back up the implementation there.
This merge request was approved by Jacek Caban.