Files
wine-access/tests/review_fixture.c
2026-10-11 22:07:14 -07:00

354 lines
17 KiB
C

// SPDX-License-Identifier: LGPL-2.1-or-later
#include <windows.h>
#include <commctrl.h>
#include <stdlib.h>
#include <string.h>
#define REVIEW_CONFIG (WM_APP + 10)
#define REVIEW_CHECK (WM_APP + 11)
#define REVIEW_CHECK_RIGHT (WM_APP + 12)
#define REVIEW_CHECK_STRICT (WM_APP + 13)
#define REVIEW_PRIVACY_CHECK (WM_APP + 14)
#define REVIEW_REMOTE_CHECK (WM_APP + 15)
static HWND editor, output, password, hidden;
static HWND mdi, mdi_active, mdi_background;
static WCHAR *large;
static WNDPROC original_edit;
static BOOL replace_during_read;
static BOOL stall_accessible;
static BOOL secondary;
static unsigned int key_down, key_up;
static const WCHAR *expected_text = L"alpha beta\r\nsecond line";
static BOOL protect_during_length, replace_list_during_read;
static unsigned int protected_text_reads;
static WNDPROC original_list;
static WNDPROC original_table;
static BOOL replace_table_during_read;
static BOOL delay_table_read, freed_table_memory, scroll_during_position;
static BOOL frame_pump;
static BOOL delay_batch;
static unsigned int synthetic_containers;
static LRESULT CALLBACK status_proc(HWND window,UINT message,WPARAM wp,LPARAM lp) {
// Like MUSHclient's status bar, report a length beyond the first NUL.
// The standard MSAA fallback retains that padding in its BSTR name.
const WCHAR text[]=L"Ready\0Connected";
if(message==WM_GETTEXTLENGTH)return (sizeof(text)/sizeof(*text))-1;
if(message==WM_GETTEXT && wp) {
size_t copied=(sizeof(text)/sizeof(*text))-1;
if(copied>wp-1)copied=wp-1;
memcpy((void*)lp,text,copied*sizeof(*text));
((WCHAR*)lp)[copied]=0;
return copied;
}
return DefWindowProcW(window,message,wp,lp);
}
static LRESULT CALLBACK container_proc(HWND window,UINT message,WPARAM wp,LPARAM lp) {
if(message==WM_GETOBJECT && lp==OBJID_QUERYCLASSNAMEIDX){++synthetic_containers;Sleep(25);}
return DefWindowProcW(window,message,wp,lp);
}
static LRESULT CALLBACK table_proc(HWND window, UINT message, WPARAM wp, LPARAM lp) {
if (message == LVM_GETITEMTEXTW && delay_table_read) {
delay_table_read=FALSE;
Sleep(650);
MEMORY_BASIC_INFORMATION memory={0};
if (!VirtualQuery((void*)lp,&memory,sizeof(memory)) || memory.State != MEM_COMMIT) {
freed_table_memory=TRUE;
return 0; // Observe the unsafe lifetime without dereferencing freed data.
}
}
LRESULT result=CallWindowProcW(original_table,window,message,wp,lp);
if (message == LVM_GETITEMTEXTW && replace_table_during_read) {
replace_table_during_read=FALSE;
LVITEMW item={0}; item.pszText=L"other & item";
SendMessageW(window,LVM_SETITEMTEXTW,0,(LPARAM)&item);
}
return result;
}
static LRESULT CALLBACK list_proc(HWND window, UINT message, WPARAM wp, LPARAM lp) {
LRESULT result = CallWindowProcW(original_list,window,message,wp,lp);
if (message == LB_GETTEXT && replace_list_during_read) {
replace_list_during_read = FALSE;
SendMessageW(window,LB_DELETESTRING,0,0);
SendMessageW(window,LB_INSERTSTRING,0,(LPARAM)L"update & item");
}
return result;
}
static LRESULT CALLBACK stall_proc(HWND window, UINT message, WPARAM wp, LPARAM lp) {
if (message == WM_GETOBJECT && stall_accessible) {
stall_accessible = FALSE;
Sleep(2000);
}
return DefWindowProcW(window,message,wp,lp);
}
static LRESULT CALLBACK edit_proc(HWND window, UINT message, WPARAM wp, LPARAM lp) {
if(message==EM_LINEINDEX && delay_batch){delay_batch=FALSE;Sleep(650);}
if (message == WM_GETTEXT && (GetWindowLongPtrW(window,GWL_STYLE) & ES_PASSWORD)) ++protected_text_reads;
if (message == WM_KEYDOWN) ++key_down;
if (message == WM_KEYUP) ++key_up;
LRESULT result = CallWindowProcW(original_edit, window, message, wp, lp);
if (message == WM_GETTEXTLENGTH && protect_during_length) {
protect_during_length = FALSE;
SendMessageW(window,EM_SETPASSWORDCHAR,L'*',0);
}
if (message == WM_GETTEXT && replace_during_read) {
replace_during_read = FALSE;
SetWindowTextW(window, L"omega zeta\r\nsecond line");
}
if (message == EM_POSFROMCHAR && scroll_during_position) {
scroll_during_position=FALSE;
SendMessageW(window,EM_LINESCROLL,0,1);
}
return result;
}
static LRESULT CALLBACK window_proc(HWND window, UINT message, WPARAM wp, LPARAM lp) {
(void)lp;
if (message == WM_TIMER && secondary) {
KillTimer(window,1);
SetForegroundWindow(window);
SetFocus(GetDlgItem(window,105));
return 0;
}
if (message == REVIEW_CONFIG) {
frame_pump=FALSE;
delay_batch=FALSE;
synthetic_containers=0;
ShowWindow(GetDlgItem(window,112),SW_HIDE);
ShowWindow(GetDlgItem(window,114),SW_HIDE);
ShowWindow(mdi,SW_HIDE);
SendMessageW(editor,EM_SETPASSWORDCHAR,0,0);
protect_during_length = replace_list_during_read = FALSE;
replace_table_during_read=FALSE;
delay_table_read=freed_table_memory=scroll_during_position=FALSE;
protected_text_reads = 0;
key_down = key_up = 0;
expected_text = L"alpha beta\r\nsecond line";
ShowWindow(GetDlgItem(window,106),SW_SHOW);
ShowWindow(GetDlgItem(window,107),SW_HIDE);
ShowWindow(GetDlgItem(window,109),SW_HIDE);
ShowWindow(GetDlgItem(window,110),SW_HIDE);
ShowWindow(GetDlgItem(window,111),SW_HIDE);
SetWindowTextW(editor, L"alpha beta\r\nsecond line");
SetWindowTextW(output, L"output first\r\noutput second");
SendMessageW(editor, EM_SETREADONLY, FALSE, 0);
SendMessageW(editor, EM_SETSEL, 0, 0);
MoveWindow(editor, 20, 35, 320, 90, TRUE);
switch (wp) {
case 33: ShowWindow(GetDlgItem(window,114),SW_SHOW); break;
case 32: {
WCHAR text[16384]; unsigned used = 0;
for (unsigned row = 0; row < 400; ++row) {
const WCHAR line[] = L"deep clipped visible line\r\n";
for (unsigned j = 0; j < (sizeof(line)/sizeof(*line))-1; ++j) text[used++] = line[j];
}
text[used] = 0;
SetWindowTextW(editor, text);
MoveWindow(editor, 20, -5000, 320, 5100, TRUE);
break;
}
case 30:
ShowWindow(mdi,SW_SHOW);
SendMessageW(mdi,WM_MDIACTIVATE,(WPARAM)mdi_active,0);
SendMessageW(mdi,WM_MDIMAXIMIZE,(WPARAM)mdi_active,0);
break;
case 31:
ShowWindow(mdi,SW_SHOW);
SendMessageW(mdi,WM_MDIRESTORE,(WPARAM)mdi_active,0);
SendMessageW(mdi,WM_MDIRESTORE,(WPARAM)mdi_background,0);
MoveWindow(mdi_background,0,0,240,300,TRUE);
MoveWindow(mdi_active,245,0,240,300,TRUE);
SendMessageW(mdi,WM_MDIACTIVATE,(WPARAM)mdi_active,0);
break;
case 28: {
WCHAR text[4096];unsigned used=0;
for(unsigned row=0;row<80;++row) {
const WCHAR line[]=L"top clipped visible line\r\n";
for(unsigned j=0;j<(sizeof(line)/sizeof(*line))-1;++j)text[used++]=line[j];
}
text[used]=0;SetWindowTextW(editor,text);
MoveWindow(editor,20,-250,320,380,TRUE);
break;
}
case 29:ShowWindow(GetDlgItem(window,112),SW_SHOW);break;
case 27:delay_batch=TRUE;break;
case 26: {
WCHAR text[4096];unsigned used=0;
for(unsigned row=0;row<80;++row) {
const WCHAR line[]=L"stable buffered output line\r\n";
for(unsigned j=0;j<(sizeof(line)/sizeof(*line))-1;++j)text[used++]=line[j];
}
text[used]=0;SetWindowTextW(editor,text);
MoveWindow(editor,20,35,320,380,TRUE);
ShowWindow(output,SW_HIDE);ShowWindow(GetDlgItem(window,106),SW_HIDE);
frame_pump=TRUE;
break;
}
case 1: // surrogate before first displayed row, absolute offsets
SetWindowTextW(editor, L"\U0001f600alpha\r\nbeta\r\nlast");
SendMessageW(editor, EM_LINESCROLL, 0, 1);
break;
case 2:
SetWindowTextW(editor, L"wrapped words repeated wrapped words repeated wrapped words repeated wrapped words\r\n\r\nlast");
MoveWindow(editor, 20, 35, 150, 150, TRUE);
break;
case 3: SetWindowTextW(editor, L""); break;
case 4:
SetForegroundWindow(window);
SetFocus(password);
return 1;
case 5:
MoveWindow(editor, 500, 35, 320, 90, TRUE);
break;
case 6: expected_text = L"omega zeta\r\nsecond line"; SetWindowTextW(editor, expected_text); break;
case 7:
large = malloc(1000002 * sizeof(WCHAR));
if (!large) ExitProcess(3);
// Avoid quadratic native wrapping work in the fixture itself.
for (int i = 0; i < 1000001; ++i) large[i] = i % 32 == 31 ? L'\n' : L'x';
large[1000001] = 0;
SendMessageW(editor, EM_LIMITTEXT, 2000000, 0);
SetWindowTextW(editor, large);
free(large); large = NULL;
break;
case 8: SetWindowPos(window, NULL, -20, -20, 0, 0, SWP_NOSIZE | SWP_NOZORDER); break;
case 9: replace_during_read = TRUE; break;
case 10: stall_accessible = TRUE; break;
case 11:
SetForegroundWindow(window);
SetFocus(output);
return 1;
case 12: ShowWindow(GetDlgItem(window,106),SW_HIDE); break;
case 14: protect_during_length = TRUE; break;
case 15: replace_list_during_read = TRUE; break;
case 17: ShowWindow(GetDlgItem(window,107),SW_SHOW); break;
case 18: ShowWindow(GetDlgItem(window,107),SW_SHOW); replace_table_during_read=TRUE; break;
case 21: ShowWindow(GetDlgItem(window,107),SW_SHOW); delay_table_read=TRUE; break;
case 22:
ShowWindow(GetDlgItem(window,109),SW_SHOW);
SendMessageW(GetDlgItem(window,109),EM_SETSEL,INT_MAX,INT_MAX);
SendMessageW(GetDlgItem(window,109),EM_SCROLLCARET,0,0);
break;
case 23:
SetWindowTextW(editor,L"first\r\nsecond\r\nthird\r\nfourth\r\nfifth\r\nsixth\r\nseventh\r\neighth\r\nninth\r\ntenth");
scroll_during_position=TRUE;
break;
case 24:
ShowWindow(GetDlgItem(window,110),SW_SHOW);
SendMessageW(GetDlgItem(window,110),EM_SETSEL,INT_MAX,INT_MAX);
SendMessageW(GetDlgItem(window,110),EM_SCROLLCARET,0,0);
break;
case 25: ShowWindow(GetDlgItem(window,111),SW_SHOW); break;
}
SetForegroundWindow(window);
SetFocus(editor);
return 1;
}
if (message == REVIEW_PRIVACY_CHECK) return protected_text_reads == 0;
if (message == WM_APP+16) return synthetic_containers==0;
if (message == REVIEW_REMOTE_CHECK) return !freed_table_memory;
if (message == REVIEW_CHECK || message == REVIEW_CHECK_RIGHT || message == REVIEW_CHECK_STRICT) {
DWORD start = 0, end = 0;
SendMessageW(editor, EM_GETSEL, (WPARAM)&start, (LPARAM)&end);
if (message == REVIEW_CHECK) return start == 0 && end == 0 && GetFocus() == editor;
WCHAR text[128], output_text[128];
GetWindowTextW(editor,text,128); GetWindowTextW(output,output_text,128);
if (message == REVIEW_CHECK_RIGHT)
return start == 1 && end == 1 && key_down == 1 && key_up == 1 && GetFocus() == editor && lstrcmpW(text,expected_text) == 0;
return start == 0 && end == 0 && GetFocus() == editor && key_down == 0 && key_up == 0 &&
lstrcmpW(text,expected_text) == 0 && lstrcmpW(output_text,L"output first\r\noutput second") == 0;
}
if (message == WM_CLOSE) { DestroyWindow(window); return 0; }
if (message == WM_DESTROY) { PostQuitMessage(0); return 0; }
return DefWindowProcW(window, message, wp, lp);
}
int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show) {
(void)previous; (void)show;
secondary=strcmp(args,"--secondary")==0;
if (strncmp(args,"--configure ",12)==0 || strcmp(args,"--check")==0 || strcmp(args,"--check-strict")==0 || strcmp(args,"--check-right")==0 || strcmp(args,"--check-remote")==0 || strcmp(args,"--close")==0) {
HWND target=FindWindowW(L"WineAccessReviewFixture",NULL);
if (!target) return 3;
if (strcmp(args,"--close")==0) { PostMessageW(target,WM_CLOSE,0,0); return 0; }
DWORD_PTR result=0;
UINT message=strcmp(args,"--check")==0 ? REVIEW_CHECK : strcmp(args,"--check-strict")==0 ? REVIEW_CHECK_STRICT : strcmp(args,"--check-right")==0 ? REVIEW_CHECK_RIGHT : strcmp(args,"--check-remote")==0 ? REVIEW_REMOTE_CHECK : REVIEW_CONFIG;
WPARAM mode=message==REVIEW_CONFIG ? (WPARAM)atoi(args+12) : 0;
if (!SendMessageTimeoutW(target,message,mode,0,SMTO_ABORTIFHUNG,3000,&result)) return 4;
return result ? 0 : 5;
}
WNDCLASSW cls = {0};
cls.hInstance = instance; cls.lpfnWndProc = window_proc;
cls.lpszClassName = secondary ? L"WineAccessReviewSecondary" : L"WineAccessReviewFixture";
cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
RegisterClassW(&cls);
WNDCLASSW stall=cls; stall.lpszClassName=L"WineReviewStall"; stall.lpfnWndProc=stall_proc;
RegisterClassW(&stall);
HWND window = CreateWindowW(cls.lpszClassName, secondary ? L"Wine secondary fixture" : L"Wine review fixture",
WS_OVERLAPPEDWINDOW, 100, 100, 560, 500, NULL, NULL, instance, NULL);
CreateWindowW(L"Static", L"&Command:", WS_CHILD | WS_VISIBLE, 20, 10, 200, 20,
window, NULL, instance, NULL);
editor = CreateWindowW(L"Edit", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER |
ES_MULTILINE | ES_AUTOVSCROLL | WS_VSCROLL, 20, 35, 320, 90, window, (HMENU)101, instance, NULL);
original_edit = (WNDPROC)SetWindowLongPtrW(editor, GWLP_WNDPROC, (LONG_PTR)edit_proc);
output = CreateWindowW(L"Edit", L"output first\r\noutput second", WS_CHILD | WS_VISIBLE |
ES_MULTILINE | ES_READONLY | WS_BORDER, 20, 205, 320, 80, window, (HMENU)102, instance, NULL);
password = CreateWindowW(L"Edit", L"private-review-secret", WS_CHILD | WS_VISIBLE |
ES_PASSWORD | WS_BORDER, 20, 320, 200, 25, window, (HMENU)103, instance, NULL);
hidden = CreateWindowW(L"Static", L"hidden-review-secret", WS_CHILD, 20, 350, 200, 25,
window, (HMENU)104, instance, NULL);
CreateWindowW(L"Button", L"&Open && close", WS_CHILD | WS_VISIBLE, 20, 390, 160, 25,
window, (HMENU)105, instance, NULL);
HWND list = CreateWindowW(L"ListBox", L"", WS_CHILD | WS_VISIBLE | LBS_NOTIFY,
350, 35, 150, 70, window, (HMENU)106, instance, NULL);
original_list = (WNDPROC)SetWindowLongPtrW(list,GWLP_WNDPROC,(LONG_PTR)list_proc);
SendMessageW(list, LB_ADDSTRING, 0, (LPARAM)L"native & item");
SendMessageW(list, LB_ADDSTRING, 0, (LPARAM)L"another item");
INITCOMMONCONTROLSEX controls = {sizeof(controls), ICC_LISTVIEW_CLASSES};
InitCommonControlsEx(&controls);
HWND table = CreateWindowW(WC_LISTVIEWW,L"",WS_CHILD|LVS_REPORT,
350,200,170,100,window,(HMENU)107,instance,NULL);
LVCOLUMNW column = {0}; column.mask=LVCF_TEXT|LVCF_WIDTH; column.cx=100; column.pszText=L"Plugin";
SendMessageW(table,LVM_INSERTCOLUMNW,0,(LPARAM)&column);
column.cx=60; column.pszText=L"State";
SendMessageW(table,LVM_INSERTCOLUMNW,1,(LPARAM)&column);
LVITEMW item={0}; item.mask=LVIF_TEXT; item.pszText=L"table & item";
SendMessageW(table,LVM_INSERTITEMW,0,(LPARAM)&item);
item.iSubItem=1; item.pszText=L"ready";
SendMessageW(table,LVM_SETITEMTEXTW,0,(LPARAM)&item);
original_table=(WNDPROC)SetWindowLongPtrW(table,GWLP_WNDPROC,(LONG_PTR)table_proc);
CreateWindowW(L"Edit",L"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz visible suffix",
WS_CHILD|ES_AUTOHSCROLL|WS_BORDER,20,130,150,25,window,(HMENU)109,instance,NULL);
CreateWindowW(L"Edit",L"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz visible suffix",
WS_CHILD|ES_MULTILINE|ES_AUTOHSCROLL|WS_HSCROLL|WS_BORDER,20,165,150,50,window,(HMENU)110,instance,NULL);
CreateWindowW(L"Static",L"literal & label",WS_CHILD|SS_NOPREFIX,
20,365,200,20,window,(HMENU)111,instance,NULL);
CreateWindowW(stall.lpszClassName,L"",WS_CHILD|WS_VISIBLE,350,150,150,20,window,NULL,instance,NULL);
WNDCLASSW container={0};container.lpfnWndProc=container_proc;container.hInstance=instance;container.lpszClassName=L"WineAccessNativeContainer";
RegisterClassW(&container);
HWND parent=CreateWindowW(container.lpszClassName,L"",WS_CHILD,20,35,320,100,window,(HMENU)112,instance,NULL);
for(unsigned level=0;level<8;++level)parent=CreateWindowW(container.lpszClassName,L"",WS_CHILD|WS_VISIBLE,0,0,320,100,parent,NULL,instance,NULL);
CreateWindowW(L"Edit",L"nested output ready",WS_CHILD|WS_VISIBLE|ES_READONLY,0,0,320,80,parent,NULL,instance,NULL);
WNDCLASSW mdi_child={0};mdi_child.lpfnWndProc=DefMDIChildProcW;mdi_child.hInstance=instance;mdi_child.lpszClassName=L"WineAccessMdiChild";
RegisterClassW(&mdi_child);
CLIENTCREATESTRUCT client_create={NULL,200};
mdi=CreateWindowW(L"MDIClient",L"",WS_CHILD|WS_CLIPCHILDREN,20,35,500,350,window,(HMENU)113,instance,&client_create);
mdi_background=CreateMDIWindowW(mdi_child.lpszClassName,L"Background",WS_OVERLAPPEDWINDOW|WS_VISIBLE,0,0,240,300,mdi,instance,0);
CreateWindowW(L"Edit",L"covered mdi text",WS_CHILD|WS_VISIBLE|ES_READONLY,0,0,220,100,mdi_background,NULL,instance,NULL);
mdi_active=CreateMDIWindowW(mdi_child.lpszClassName,L"Active",WS_OVERLAPPEDWINDOW|WS_VISIBLE,245,0,240,300,mdi,instance,0);
CreateWindowW(L"Edit",L"active mdi text",WS_CHILD|WS_VISIBLE|ES_READONLY,0,0,220,100,mdi_active,NULL,instance,NULL);
WNDCLASSW status=cls;status.lpszClassName=L"WineAccessStatusFixture";status.lpfnWndProc=status_proc;
RegisterClassW(&status);
CreateWindowW(status.lpszClassName,L"",WS_CHILD,20,420,480,20,window,(HMENU)114,instance,NULL);
ShowWindow(window, SW_SHOW);
SendMessageW(window, REVIEW_CONFIG, 0, 0);
if (secondary) SetTimer(window,1,400,NULL);
MSG message;
for (;;) {
if (frame_pump) {
if (!PeekMessageW(&message,NULL,0,0,PM_REMOVE)) { Sleep(16);continue; }
if (message.message==WM_QUIT)break;
} else if (GetMessageW(&message,NULL,0,0)<=0)break;
TranslateMessage(&message); DispatchMessageW(&message);
}
return 0;
}