Hi, This patch adds a conformance test of riched20's EM_FINDTEXT and EM_FINDTEXTEX. It also fixes three issues found in the implementation. 2006-02-09 Thomas Kho * dlls/riched20/editor.c: riched20: Fixed bounds error when finding text forward (bug 4479) Fixed a potential null-pointer dereference (by Lei Zhang) Corrected find behavior for null-results * configure.ac, dlls/riched20/Makefile.in, dlls/riched20/tests/Makefile.in, dlls/riched20/tests/editor.c: riched20: added tests for EM_FINDTEXT and EM_FINDTEXTEX messages Without the fix to dlls/riched20/editor.c, the following test case to search for the empty string crashes on Wine: + /* Find nothing */ + {5, 10, "", FR_DOWN, -1, 0}, configure.ac | 1 dlls/riched20/Makefile.in | 2 dlls/riched20/editor.c | 20 ++- dlls/riched20/tests/Makefile.in | 13 ++ dlls/riched20/tests/editor.c | 215 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 245 insertions(+), 6 deletions(-) Signed-off-by: Thomas Kho diff -Naur ../cvs/configure.ac ./configure.ac --- ../cvs/configure.ac 2006-02-06 13:06:07.000000000 -0800 +++ ./configure.ac 2006-02-09 11:53:10.000000000 -0800 @@ -1578,6 +1578,7 @@ dlls/quartz/tests/Makefile dlls/rasapi32/Makefile dlls/riched20/Makefile +dlls/riched20/tests/Makefile dlls/richedit/Makefile dlls/rpcrt4/Makefile dlls/rpcrt4/tests/Makefile diff -Naur ../cvs/dlls/riched20/Makefile.in ./dlls/riched20/Makefile.in --- ../cvs/dlls/riched20/Makefile.in 2006-02-01 05:00:48.000000000 -0800 +++ ./dlls/riched20/Makefile.in 2006-02-09 11:53:10.000000000 -0800 @@ -25,6 +25,8 @@ wrap.c \ writer.c +SUBDIRS = tests + @MAKE_DLL_RULES@ ### Dependencies: diff -Naur ../cvs/dlls/riched20/editor.c ./dlls/riched20/editor.c --- ../cvs/dlls/riched20/editor.c 2006-02-06 03:11:43.000000000 -0800 +++ ./dlls/riched20/editor.c 2006-02-09 11:53:57.000000000 -0800 @@ -755,23 +755,26 @@ nMax = max(chrg->cpMin, chrg->cpMax); } - if (!nLen) + if (!nLen || nMin < 0 || nMax < 0) { if (chrgText) - chrgText->cpMin = chrgText->cpMax = ((flags & FR_DOWN) ? nMin : nMax); - return chrgText->cpMin; + chrgText->cpMin = chrgText->cpMax = -1; + return -1; } if (flags & FR_DOWN) /* Forward search */ { nStart = nMin; item = ME_FindItemAtOffset(editor, diRun, nStart, &nStart); - if (!item) + if (!item) { + if (chrgText) + chrgText->cpMin = chrgText->cpMax = -1; return -1; + } para = ME_GetParagraph(item); while (item - && para->member.para.nCharOfs + item->member.run.nCharOfs + nStart + nLen < nMax) + && para->member.para.nCharOfs + item->member.run.nCharOfs + nStart + nLen <= nMax) { ME_DisplayItem *pCurItem = item; int nCurStart = nStart; @@ -811,8 +814,11 @@ { nEnd = nMax; item = ME_FindItemAtOffset(editor, diRun, nEnd, &nEnd); - if (!item) + if (!item) { + if (chrgText) + chrgText->cpMin = chrgText->cpMax = -1; return -1; + } para = ME_GetParagraph(item); @@ -854,6 +860,8 @@ } } TRACE("not found\n"); + if (chrgText) + chrgText->cpMin = chrgText->cpMax = -1; return -1; } diff -Naur ../cvs/dlls/riched20/tests/editor.c ./dlls/riched20/tests/editor.c --- ../cvs/dlls/riched20/tests/editor.c 1969-12-31 16:00:00.000000000 -0800 +++ ./dlls/riched20/tests/editor.c 2006-02-09 11:53:10.000000000 -0800 @@ -0,0 +1,215 @@ +/* +* Unit test suite for rich edit control +* +* Copyright 2006 Google (Thomas Kho) +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include +#include +#include + +static HMODULE hmoduleRichEdit; + +static HWND new_window(LPCTSTR lpClassName, DWORD dwStyle, HWND parent) { + HWND hwnd; + hwnd = CreateWindow(lpClassName, NULL, dwStyle|WS_POPUP|WS_HSCROLL|WS_VSCROLL + |WS_VISIBLE, 0, 0, 200, 60, parent, NULL, + hmoduleRichEdit, NULL); + ok(hwnd != NULL, "class: %s, error: %d\n", lpClassName, (int) GetLastError()); + return hwnd; +} + +static HWND new_richedit(HWND parent) { + return new_window(RICHEDIT_CLASS, ES_MULTILINE, parent); +} + +static const char haystack[] = "WINEWine wineWine wine WineWine"; + /* ^0 ^10 ^20 ^30 */ + +struct find_s { + int start; + int end; + char *needle; + int flags; + int expected_loc; + int _todo_wine; +}; + +struct find_s find_tests[] = { + /* Find in empty text */ + {0, -1, "foo", FR_DOWN, -1, 0}, + {0, -1, "foo", 0, -1, 0}, + {0, -1, "", FR_DOWN, -1, 0}, + {20, 5, "foo", FR_DOWN, -1, 0}, + {5, 20, "foo", FR_DOWN, -1, 0} +}; + +struct find_s find_tests2[] = { + /* No-result find */ + {0, -1, "foo", FR_DOWN | FR_MATCHCASE, -1, 0}, + {5, 20, "WINE", FR_DOWN | FR_MATCHCASE, -1, 0}, + + /* Subsequent finds */ + {0, -1, "Wine", FR_DOWN | FR_MATCHCASE, 4, 0}, + {5, 31, "Wine", FR_DOWN | FR_MATCHCASE, 13, 0}, + {14, 31, "Wine", FR_DOWN | FR_MATCHCASE, 23, 0}, + {24, 31, "Wine", FR_DOWN | FR_MATCHCASE, 27, 0}, + + /* Find backwards */ + {19, 20, "Wine", FR_MATCHCASE, 13, 1}, + {10, 20, "Wine", FR_MATCHCASE, 4, 1}, + {20, 10, "Wine", FR_MATCHCASE, 13, 0}, + + /* Case-insensitive */ + {1, 31, "wInE", FR_DOWN, 4, 1}, + {1, 31, "Wine", FR_DOWN, 4, 0}, + + /* High-to-low ranges */ + {20, 5, "Wine", FR_DOWN, -1, 1}, + {2, 1, "Wine", FR_DOWN, -1, 0}, + {30, 29, "Wine", FR_DOWN, -1, 0}, + {20, 5, "Wine", 0, 13, 0}, + + /* Find nothing */ + {5, 10, "", FR_DOWN, -1, 0}, + {10, 5, "", FR_DOWN, -1, 0}, + {0, -1, "", FR_DOWN, -1, 0}, + {10, 5, "", 0, -1, 0}, + + /* Whole-word search */ + {0, -1, "wine", FR_DOWN | FR_WHOLEWORD, 18, 1}, + {0, -1, "win", FR_DOWN | FR_WHOLEWORD, -1, 1}, + + /* Bad ranges */ + {-20, 20, "Wine", FR_DOWN, -1, 0}, + {-20, 20, "Wine", FR_DOWN, -1, 0}, + {-15, -20, "Wine", FR_DOWN, -1, 0}, + {1<<12, 1<<13, "Wine", FR_DOWN, -1, 0}, + + /* Check the case noted in bug 4479 where matches at end aren't recognized */ + {23, 31, "Wine", FR_DOWN | FR_MATCHCASE, 23, 0}, + {27, 31, "Wine", FR_DOWN | FR_MATCHCASE, 27, 0}, + {27, 32, "Wine", FR_DOWN | FR_MATCHCASE, 27, 0}, + {13, 31, "WineWine", FR_DOWN | FR_MATCHCASE, 23, 0}, + {13, 32, "WineWine", FR_DOWN | FR_MATCHCASE, 23, 0}, + + /* The backwards case of bug 4479; bounds look right + * Fails because backward find is wrong */ + {19, 20, "WINE", FR_MATCHCASE, 0, 1}, + {0, 20, "WINE", FR_MATCHCASE, -1, 1} +}; + +static void check_EM_FINDTEXT(HWND hwnd, char *name, struct find_s *f, int id) { + int findloc; + FINDTEXT ft; + memset(&ft, 0, sizeof(ft)); + ft.chrg.cpMin = f->start; + ft.chrg.cpMax = f->end; + ft.lpstrText = f->needle; + findloc = SendMessage(hwnd, EM_FINDTEXT, f->flags, (LPARAM) &ft); + ok(findloc == f->expected_loc, + "EM_FINDTEXT(%s,%d) '%s' in range(%d,%d), flags %08x, got start at %d\n", + name, id, f->needle, f->start, f->end, f->flags, findloc); +} + +static void check_EM_FINDTEXTEX(HWND hwnd, char *name, struct find_s *f, + int id) { + int findloc; + FINDTEXTEX ft; + memset(&ft, 0, sizeof(ft)); + ft.chrg.cpMin = f->start; + ft.chrg.cpMax = f->end; + ft.lpstrText = f->needle; + findloc = SendMessage(hwnd, EM_FINDTEXTEX, f->flags, (LPARAM) &ft); + ok(findloc == f->expected_loc, + "EM_FINDTEXTEX(%s,%d) '%s' in range(%d,%d), flags %08x, start at %d\n", + name, id, f->needle, f->start, f->end, f->flags, findloc); + ok(ft.chrgText.cpMin == f->expected_loc, + "EM_FINDTEXTEX(%s,%d) '%s' in range(%d,%d), flags %08x, start at %ld\n", + name, id, f->needle, f->start, f->end, f->flags, ft.chrgText.cpMin); + ok(ft.chrgText.cpMax == ((f->expected_loc == -1) ? -1 + : f->expected_loc + strlen(f->needle)), + "EM_FINDTEXTEX(%s,%d) '%s' in range(%d,%d), flags %08x, end at %ld\n", + name, id, f->needle, f->start, f->end, f->flags, ft.chrgText.cpMax); +} + +static void run_tests_EM_FINDTEXT(HWND hwnd, char *name, struct find_s *find, + int num_tests) +{ + int i; + + for (i = 0; i < num_tests; i++) { + if (find[i]._todo_wine) { + todo_wine { + check_EM_FINDTEXT(hwnd, name, &find[i], i); + check_EM_FINDTEXTEX(hwnd, name, &find[i], i); + } + } else { + check_EM_FINDTEXT(hwnd, name, &find[i], i); + check_EM_FINDTEXTEX(hwnd, name, &find[i], i); + } + } +} + +static void test_EM_FINDTEXT(void) +{ + HWND hwndRichEdit = new_richedit(NULL); + + run_tests_EM_FINDTEXT(hwndRichEdit, "1", find_tests, + sizeof(find_tests)/sizeof(struct find_s)); + + SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) haystack); + + run_tests_EM_FINDTEXT(hwndRichEdit, "2", find_tests2, + sizeof(find_tests2)/sizeof(struct find_s)); + + DestroyWindow(hwndRichEdit); +} + +START_TEST( editor ) +{ + MSG msg; + time_t end; + + /* Must explicitly LoadLibrary(). The test has no references to functions in + * RICHED20.DLL, so the linker doesn't actually link to it. */ + hmoduleRichEdit = LoadLibrary("RICHED20.DLL"); + ok(hmoduleRichEdit != NULL, "error: %d\n", (int) GetLastError()); + + test_EM_FINDTEXT(); + + /* Set the environment variable WINETEST_RICHED20 to keep windows + * responsive and open for 30 seconds. This is useful for debugging. + * + * The message pump uses PeekMessage() to empty the queue and then sleeps for + * 50ms before retrying the queue. */ + end = time(NULL) + 30; + if (getenv( "WINETEST_RICHED20" )) { + while (time(NULL) < end) { + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } else { + Sleep(50); + } + } + } + + ok(FreeLibrary(hmoduleRichEdit) != 0, "error: %d\n", (int) GetLastError()); +} + diff -Naur ../cvs/dlls/riched20/tests/Makefile.in ./dlls/riched20/tests/Makefile.in --- ../cvs/dlls/riched20/tests/Makefile.in 1969-12-31 16:00:00.000000000 -0800 +++ ./dlls/riched20/tests/Makefile.in 2006-02-09 11:53:10.000000000 -0800 @@ -0,0 +1,13 @@ +TOPSRCDIR = @top_srcdir@ +TOPOBJDIR = ../../.. +SRCDIR = @srcdir@ +VPATH = @srcdir@ +TESTDLL = riched20.dll +IMPORTS = riched20 user32 gdi32 kernel32 + +CTESTS = \ + editor.c + +@MAKE_TEST_RULES@ + +### Dependencies: