Wine-devel
Threads by month
- ----- 2026 -----
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2002 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2001 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
December 2017
- 60 participants
- 368 discussions
Based on a patch by Ryan Schmidt.
Signed-off-by: Hans Leidekker <hans(a)codeweavers.com>
---
dlls/bcrypt/bcrypt_main.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dlls/bcrypt/bcrypt_main.c b/dlls/bcrypt/bcrypt_main.c
index 3bdc463904..35c0e4a076 100644
--- a/dlls/bcrypt/bcrypt_main.c
+++ b/dlls/bcrypt/bcrypt_main.c
@@ -949,13 +949,13 @@ static NTSTATUS key_set_params( struct key *key, UCHAR *iv, ULONG iv_len )
key->ref_decrypt = NULL;
}
- if ((status = CCCryptorCreateWithMode( kCCEncrypt, kCCModeCBC, kCCAlgorithmAES, ccNoPadding, iv,
+ if ((status = CCCryptorCreateWithMode( kCCEncrypt, kCCModeCBC, kCCAlgorithmAES128, ccNoPadding, iv,
key->secret, key->secret_len, NULL, 0, 0, 0, &key->ref_encrypt )) != kCCSuccess)
{
WARN( "CCCryptorCreateWithMode failed %d\n", status );
return STATUS_INTERNAL_ERROR;
}
- if ((status = CCCryptorCreateWithMode( kCCDecrypt, kCCModeCBC, kCCAlgorithmAES, ccNoPadding, iv,
+ if ((status = CCCryptorCreateWithMode( kCCDecrypt, kCCModeCBC, kCCAlgorithmAES128, ccNoPadding, iv,
key->secret, key->secret_len, NULL, 0, 0, 0, &key->ref_decrypt )) != kCCSuccess)
{
WARN( "CCCryptorCreateWithMode failed %d\n", status );
--
2.11.0
1
0
19 Dec '17
I was instructed to send this patch to this mailing list.
It fixes https://bugs.winehq.org/show_bug.cgi?id=44173
2
2
19 Dec '17
Signed-off-by: Nikolay Sivov <nsivov(a)codeweavers.com>
---
dlls/comctl32/tests/edit.c | 33 +++++++++++----------------------
1 file changed, 11 insertions(+), 22 deletions(-)
diff --git a/dlls/comctl32/tests/edit.c b/dlls/comctl32/tests/edit.c
index 1453271986..9feb9b42bc 100644
--- a/dlls/comctl32/tests/edit.c
+++ b/dlls/comctl32/tests/edit.c
@@ -1675,40 +1675,30 @@ static void test_margins_font_change(void)
SendMessageA(hwEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(0,0));
SendMessageA(hwEdit, WM_SETFONT, (WPARAM)hfont, 0);
margins = SendMessageA(hwEdit, EM_GETMARGINS, 0, 0);
- ok(LOWORD(margins) == 0 || broken(LOWORD(margins) == LOWORD(font_margins)), /* win95 */
- "got %d\n", LOWORD(margins));
- ok(HIWORD(margins) == 0 || broken(HIWORD(margins) == HIWORD(font_margins)), /* win95 */
- "got %d\n", HIWORD(margins));
+ ok(LOWORD(margins) == 0, "got %d\n", LOWORD(margins));
+ ok(HIWORD(margins) == 0, "got %d\n", HIWORD(margins));
SendMessageA(hwEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(1,0));
SendMessageA(hwEdit, WM_SETFONT, (WPARAM)hfont, 0);
margins = SendMessageA(hwEdit, EM_GETMARGINS, 0, 0);
- ok(LOWORD(margins) == 1 || broken(LOWORD(margins) == LOWORD(font_margins)), /* win95 */
- "got %d\n", LOWORD(margins));
- ok(HIWORD(margins) == 0 || broken(HIWORD(margins) == HIWORD(font_margins)), /* win95 */
- "got %d\n", HIWORD(margins));
+ ok(LOWORD(margins) == 1, "got %d\n", LOWORD(margins));
+ ok(HIWORD(margins) == 0, "got %d\n", HIWORD(margins));
SendMessageA(hwEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(1,1));
SendMessageA(hwEdit, WM_SETFONT, (WPARAM)hfont, 0);
margins = SendMessageA(hwEdit, EM_GETMARGINS, 0, 0);
- ok(LOWORD(margins) == 1 || broken(LOWORD(margins) == LOWORD(font_margins)), /* win95 */
- "got %d\n", LOWORD(margins));
- ok(HIWORD(margins) == 1 || broken(HIWORD(margins) == HIWORD(font_margins)), /* win95 */
- "got %d\n", HIWORD(margins));
+ ok(LOWORD(margins) == 1, "got %d\n", LOWORD(margins));
+ ok(HIWORD(margins) == 1, "got %d\n", HIWORD(margins));
SendMessageA(hwEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(EC_USEFONTINFO,EC_USEFONTINFO));
margins = SendMessageA(hwEdit, EM_GETMARGINS, 0, 0);
- ok(LOWORD(margins) == 1 || broken(LOWORD(margins) == LOWORD(font_margins)), /* win95 */
- "got %d\n", LOWORD(margins));
- ok(HIWORD(margins) == 1 || broken(HIWORD(margins) == HIWORD(font_margins)), /* win95 */
- "got %d\n", HIWORD(margins));
+ ok(LOWORD(margins) == 1, "got %d\n", LOWORD(margins));
+ ok(HIWORD(margins) == 1, "got %d\n", HIWORD(margins));
SendMessageA(hwEdit, WM_SETFONT, (WPARAM)hfont2, 0);
margins = SendMessageA(hwEdit, EM_GETMARGINS, 0, 0);
- ok(LOWORD(margins) == 1 || broken(LOWORD(margins) != 1 && LOWORD(margins) != LOWORD(font_margins)), /* win95 */
- "got %d\n", LOWORD(margins));
- ok(HIWORD(margins) == 1 || broken(HIWORD(margins) != 1 && HIWORD(margins) != HIWORD(font_margins)), /* win95 */
- "got %d\n", HIWORD(margins));
+ ok(LOWORD(margins) == 1, "got %d\n", LOWORD(margins));
+ ok(HIWORD(margins) == 1, "got %d\n", HIWORD(margins));
/* Above a certain size threshold then the margin is updated */
SetWindowPos(hwEdit, NULL, 10, 10, 1000, 100, SWP_NOZORDER | SWP_NOACTIVATE);
@@ -1731,8 +1721,7 @@ static void test_margins_font_change(void)
ok(HIWORD(margins) == HIWORD(font_margins), "got %d\n", HIWORD(margins));
SendMessageA(hwEdit, WM_SETFONT, (WPARAM)hfont2, 0);
margins = SendMessageA(hwEdit, EM_GETMARGINS, 0, 0);
- ok(LOWORD(margins) != LOWORD(font_margins) || broken(LOWORD(margins) == LOWORD(font_margins)), /* win98 */
- "got %d\n", LOWORD(margins));
+ ok(LOWORD(margins) != LOWORD(font_margins), "got %d\n", LOWORD(margins));
ok(HIWORD(margins) != HIWORD(font_margins), "got %d\n", HIWORD(margins));
SendMessageA(hwEdit, WM_SETFONT, 0, 0);
--
2.15.1
1
0
19 Dec '17
From: Alex Henrie <alexhenrie24(a)gmail.com>
Signed-off-by: Huw Davies <huw(a)codeweavers.com>
---
dlls/gdi32/freetype.c | 2 +-
dlls/gdi32/tests/font.c | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/dlls/gdi32/freetype.c b/dlls/gdi32/freetype.c
index 660900a56e..4db1c7eb56 100644
--- a/dlls/gdi32/freetype.c
+++ b/dlls/gdi32/freetype.c
@@ -7930,7 +7930,7 @@ static BOOL get_outline_text_metrics(GdiFont *font)
font->potm->otmrcFontBox.bottom = SCALE_Y(ft_face->bbox.yMin);
font->potm->otmMacAscent = TM.tmAscent;
font->potm->otmMacDescent = -TM.tmDescent;
- font->potm->otmMacLineGap = font->potm->otmLineGap;
+ font->potm->otmMacLineGap = SCALE_Y(pHori->Line_Gap);
font->potm->otmusMinimumPPEM = 0; /* TT Header */
font->potm->otmptSubscriptSize.x = SCALE_X(pOS2->ySubscriptXSize);
font->potm->otmptSubscriptSize.y = SCALE_Y(pOS2->ySubscriptYSize);
diff --git a/dlls/gdi32/tests/font.c b/dlls/gdi32/tests/font.c
index bd04a8b6b1..117a14f153 100644
--- a/dlls/gdi32/tests/font.c
+++ b/dlls/gdi32/tests/font.c
@@ -1790,17 +1790,17 @@ static void test_GetKerningPairs(void)
kd[i].otmMacDescent, otm.otmMacDescent);
ok(near_match(kd[i].otmMacAscent, otm.otmMacAscent), "expected %d, got %d\n",
kd[i].otmMacAscent, otm.otmMacAscent);
-todo_wine {
+todo_wine
ok(kd[i].otmsCapEmHeight == otm.otmsCapEmHeight, "expected %u, got %u\n",
kd[i].otmsCapEmHeight, otm.otmsCapEmHeight);
+todo_wine
ok(kd[i].otmsXHeight == otm.otmsXHeight, "expected %u, got %u\n",
kd[i].otmsXHeight, otm.otmsXHeight);
- /* FIXME: this one sometimes succeeds due to expected 0, enable it when removing todo */
- if (0) ok(kd[i].otmMacLineGap == otm.otmMacLineGap, "expected %u, got %u\n",
+ ok(kd[i].otmMacLineGap == otm.otmMacLineGap, "expected %u, got %u\n",
kd[i].otmMacLineGap, otm.otmMacLineGap);
+todo_wine
ok(kd[i].otmusMinimumPPEM == otm.otmusMinimumPPEM, "expected %u, got %u\n",
kd[i].otmusMinimumPPEM, otm.otmusMinimumPPEM);
-}
total_kern_pairs = GetKerningPairsW(hdc, 0, NULL);
trace("total_kern_pairs %u\n", total_kern_pairs);
--
2.12.0
1
1
Signed-off-by: Nikolay Sivov <nsivov(a)codeweavers.com>
---
dlls/comctl32/tests/Makefile.in | 1 +
dlls/comctl32/tests/listbox.c | 1862 +++++++++++++++++++++++++++++++++++++++
2 files changed, 1863 insertions(+)
create mode 100644 dlls/comctl32/tests/listbox.c
diff --git a/dlls/comctl32/tests/Makefile.in b/dlls/comctl32/tests/Makefile.in
index 4fd0f0299e..f93ff2585d 100644
--- a/dlls/comctl32/tests/Makefile.in
+++ b/dlls/comctl32/tests/Makefile.in
@@ -11,6 +11,7 @@ C_SRCS = \
header.c \
imagelist.c \
ipaddress.c \
+ listbox.c \
listview.c \
misc.c \
monthcal.c \
diff --git a/dlls/comctl32/tests/listbox.c b/dlls/comctl32/tests/listbox.c
new file mode 100644
index 0000000000..23aaac29e2
--- /dev/null
+++ b/dlls/comctl32/tests/listbox.c
@@ -0,0 +1,1862 @@
+/* Unit test suite for list boxes.
+ *
+ * Copyright 2003 Ferenc Wagner
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "winnls.h"
+
+#include "wine/test.h"
+#include "v6util.h"
+
+static const char * const strings[4] = {
+ "First added",
+ "Second added",
+ "Third added",
+ "Fourth added which is very long because at some time we only had a 256 byte character buffer and "
+ "that was overflowing in one of those applications that had a common dialog file open box and tried "
+ "to add a 300 characters long custom filter string which of course the code did not like and crashed. "
+ "Just make sure this string is longer than 256 characters."
+};
+
+static const char BAD_EXTENSION[] = "*.badtxt";
+
+static HWND create_listbox(DWORD add_style, HWND parent)
+{
+ INT_PTR ctl_id = 0;
+ HWND handle;
+
+ if (parent)
+ ctl_id=1;
+
+ handle = CreateWindowA("LISTBOX", "TestList", (LBS_STANDARD & ~LBS_SORT) | add_style, 0, 0, 100, 100,
+ parent, (HMENU)ctl_id, NULL, 0);
+ ok(handle != NULL, "Failed to create listbox window.\n");
+
+ SendMessageA(handle, LB_ADDSTRING, 0, (LPARAM) strings[0]);
+ SendMessageA(handle, LB_ADDSTRING, 0, (LPARAM) strings[1]);
+ SendMessageA(handle, LB_ADDSTRING, 0, (LPARAM) strings[2]);
+ SendMessageA(handle, LB_ADDSTRING, 0, (LPARAM) strings[3]);
+
+ return handle;
+}
+
+struct listbox_prop
+{
+ DWORD add_style;
+};
+
+struct listbox_stat
+{
+ int selected, anchor, caret, selcount;
+};
+
+struct listbox_test
+{
+ struct listbox_prop prop;
+ struct listbox_stat init, init_todo;
+ struct listbox_stat click, click_todo;
+ struct listbox_stat step, step_todo;
+ struct listbox_stat sel, sel_todo;
+};
+
+static void listbox_query(HWND handle, struct listbox_stat *results)
+{
+ results->selected = SendMessageA(handle, LB_GETCURSEL, 0, 0);
+ results->anchor = SendMessageA(handle, LB_GETANCHORINDEX, 0, 0);
+ results->caret = SendMessageA(handle, LB_GETCARETINDEX, 0, 0);
+ results->selcount = SendMessageA(handle, LB_GETSELCOUNT, 0, 0);
+}
+
+static void buttonpress(HWND handle, WORD x, WORD y)
+{
+ LPARAM lp = x + (y << 16);
+
+ SendMessageA(handle, WM_LBUTTONDOWN, MK_LBUTTON, lp);
+ SendMessageA(handle, WM_LBUTTONUP, 0, lp);
+}
+
+static void keypress(HWND handle, WPARAM keycode, BYTE scancode, BOOL extended)
+{
+ LPARAM lp = 1 + (scancode << 16) + (extended ? KEYEVENTF_EXTENDEDKEY : 0);
+
+ SendMessageA(handle, WM_KEYDOWN, keycode, lp);
+ SendMessageA(handle, WM_KEYUP , keycode, lp | 0xc000000);
+}
+
+#define listbox_field_ok(t, s, f, got) \
+ ok (t.s.f==got.f, "style %#x, step " #s ", field " #f \
+ ": expected %d, got %d\n", (unsigned int)t.prop.add_style, \
+ t.s.f, got.f)
+
+#define listbox_todo_field_ok(t, s, f, got) \
+ todo_wine_if (t.s##_todo.f) { listbox_field_ok(t, s, f, got); }
+
+#define listbox_ok(t, s, got) \
+ listbox_todo_field_ok(t, s, selected, got); \
+ listbox_todo_field_ok(t, s, anchor, got); \
+ listbox_todo_field_ok(t, s, caret, got); \
+ listbox_todo_field_ok(t, s, selcount, got)
+
+static void run_test(const struct listbox_test test)
+{
+ struct listbox_stat answer;
+ HWND hLB=create_listbox (test.prop.add_style, 0);
+ RECT second_item;
+ int i, res;
+
+ listbox_query (hLB, &answer);
+ listbox_ok (test, init, answer);
+
+ SendMessageA(hLB, LB_GETITEMRECT, 1, (LPARAM) &second_item);
+ buttonpress(hLB, (WORD)second_item.left, (WORD)second_item.top);
+
+ listbox_query(hLB, &answer);
+ listbox_ok(test, click, answer);
+
+ keypress(hLB, VK_DOWN, 0x50, TRUE);
+
+ listbox_query(hLB, &answer);
+ listbox_ok(test, step, answer);
+
+ DestroyWindow(hLB);
+
+ hLB = create_listbox(test.prop.add_style, 0);
+
+ SendMessageA(hLB, LB_SELITEMRANGE, TRUE, MAKELPARAM(1, 2));
+ listbox_query(hLB, &answer);
+ listbox_ok(test, sel, answer);
+
+ for (i = 0; i < 4; i++)
+ {
+ DWORD size = SendMessageA(hLB, LB_GETTEXTLEN, i, 0);
+ int resA, resW;
+ WCHAR *txtw;
+ CHAR *txt;
+
+ txt = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size + 1);
+ resA = SendMessageA(hLB, LB_GETTEXT, i, (LPARAM)txt);
+ ok(!strcmp(txt, strings[i]), "returned string for item %d does not match %s vs %s\n", i, txt, strings[i]);
+
+ txtw = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (size + 1) * sizeof(*txtw));
+ resW = SendMessageW(hLB, LB_GETTEXT, i, (LPARAM)txtw);
+ if (resA != resW)
+ trace("SendMessageW(LB_GETTEXT) not supported on this platform (resA=%d resW=%d), skipping...\n", resA, resW);
+ else
+ {
+ WideCharToMultiByte(CP_ACP, 0, txtw, -1, txt, size, NULL, NULL);
+ ok(!strcmp (txt, strings[i]), "returned string for item %d does not match %s vs %s\n", i, txt, strings[i]);
+ }
+
+ HeapFree(GetProcessHeap(), 0, txtw);
+ HeapFree(GetProcessHeap(), 0, txt);
+ }
+
+ /* Confirm the count of items, and that an invalid delete does not remove anything */
+ res = SendMessageA(hLB, LB_GETCOUNT, 0, 0);
+ ok(res == 4, "Expected 4 items, got %d\n", res);
+ res = SendMessageA(hLB, LB_DELETESTRING, -1, 0);
+ ok(res == LB_ERR, "Expected LB_ERR items, got %d\n", res);
+ res = SendMessageA(hLB, LB_DELETESTRING, 4, 0);
+ ok(res == LB_ERR, "Expected LB_ERR items, got %d\n", res);
+ res = SendMessageA(hLB, LB_GETCOUNT, 0, 0);
+ ok(res == 4, "Expected 4 items, got %d\n", res);
+
+ DestroyWindow(hLB);
+}
+
+static void test_item_height(void)
+{
+ INT itemHeight;
+ TEXTMETRICA tm;
+ HFONT font;
+ HWND hLB;
+ HDC hdc;
+
+ hLB = create_listbox (0, 0);
+ ok ((hdc = GetDCEx( hLB, 0, DCX_CACHE )) != 0, "Can't get hdc\n");
+ ok ((font = GetCurrentObject(hdc, OBJ_FONT)) != 0, "Can't get the current font\n");
+ ok (GetTextMetricsA( hdc, &tm ), "Can't read font metrics\n");
+ ReleaseDC( hLB, hdc);
+
+ ok (SendMessageA(hLB, WM_SETFONT, (WPARAM)font, 0) == 0, "Can't set font\n");
+
+ itemHeight = SendMessageA(hLB, LB_GETITEMHEIGHT, 0, 0);
+ ok (itemHeight == tm.tmHeight, "Item height wrong, got %d, expecting %d\n", itemHeight, tm.tmHeight);
+
+ DestroyWindow (hLB);
+
+ hLB = CreateWindowA("LISTBOX", "TestList", LBS_OWNERDRAWVARIABLE, 0, 0, 100, 100, NULL, NULL, NULL, 0);
+
+ itemHeight = SendMessageA(hLB, LB_GETITEMHEIGHT, 0, 0);
+ ok(itemHeight > 0 && itemHeight <= tm.tmHeight, "Unexpected item height %d, expected %d.\n",
+ itemHeight, tm.tmHeight);
+ itemHeight = SendMessageA(hLB, LB_GETITEMHEIGHT, 5, 0);
+ ok(itemHeight > 0 && itemHeight <= tm.tmHeight, "Unexpected item height %d, expected %d.\n",
+ itemHeight, tm.tmHeight);
+ itemHeight = SendMessageA(hLB, LB_GETITEMHEIGHT, -5, 0);
+ ok(itemHeight > 0 && itemHeight <= tm.tmHeight, "Unexpected item height %d, expected %d.\n",
+ itemHeight, tm.tmHeight);
+
+ DestroyWindow (hLB);
+}
+
+static int got_selchange;
+
+static LRESULT WINAPI main_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ switch (msg)
+ {
+ case WM_DRAWITEM:
+ {
+ RECT rc_item, rc_client, rc_clip;
+ DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)lparam;
+
+ ok(wparam == dis->CtlID, "got wParam=%08lx instead of %08x\n", wparam, dis->CtlID);
+ ok(dis->CtlType == ODT_LISTBOX, "wrong CtlType %04x\n", dis->CtlType);
+
+ GetClientRect(dis->hwndItem, &rc_client);
+ GetClipBox(dis->hDC, &rc_clip);
+ ok(EqualRect(&rc_client, &rc_clip) || IsRectEmpty(&rc_clip),
+ "client rect of the listbox should be equal to the clip box,"
+ "or the clip box should be empty\n");
+
+ SendMessageA(dis->hwndItem, LB_GETITEMRECT, dis->itemID, (LPARAM)&rc_item);
+ ok(EqualRect(&dis->rcItem, &rc_item), "item rects are not equal\n");
+
+ break;
+ }
+
+ case WM_COMMAND:
+ if (HIWORD( wparam ) == LBN_SELCHANGE) got_selchange++;
+ break;
+
+ default:
+ break;
+ }
+
+ return DefWindowProcA(hwnd, msg, wparam, lparam);
+}
+
+static HWND create_parent( void )
+{
+ static ATOM class;
+ WNDCLASSA cls;
+
+ if (!class)
+ {
+ cls.style = 0;
+ cls.lpfnWndProc = main_window_proc;
+ cls.cbClsExtra = 0;
+ cls.cbWndExtra = 0;
+ cls.hInstance = GetModuleHandleA(NULL);
+ cls.hIcon = 0;
+ cls.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
+ cls.hbrBackground = GetStockObject(WHITE_BRUSH);
+ cls.lpszMenuName = NULL;
+ cls.lpszClassName = "main_window_class";
+ class = RegisterClassA( &cls );
+ }
+
+ return CreateWindowExA(0, "main_window_class", NULL, WS_POPUP | WS_VISIBLE, 100, 100, 400, 400, GetDesktopWindow(),
+ 0, GetModuleHandleA(NULL), NULL);
+}
+
+static void test_ownerdraw(void)
+{
+ HWND parent, hLB;
+ INT ret;
+ RECT rc;
+
+ parent = create_parent();
+ assert(parent);
+
+ hLB = create_listbox(LBS_OWNERDRAWFIXED | WS_CHILD | WS_VISIBLE, parent);
+ assert(hLB);
+
+ SetForegroundWindow(hLB);
+ UpdateWindow(hLB);
+
+ /* make height short enough */
+ SendMessageA(hLB, LB_GETITEMRECT, 0, (LPARAM)&rc);
+ SetWindowPos(hLB, 0, 0, 0, 100, rc.bottom - rc.top + 1, SWP_NOZORDER | SWP_NOMOVE);
+
+ /* make 0 item invisible */
+ SendMessageA(hLB, LB_SETTOPINDEX, 1, 0);
+ ret = SendMessageA(hLB, LB_GETTOPINDEX, 0, 0);
+ ok(ret == 1, "wrong top index %d\n", ret);
+
+ SendMessageA(hLB, LB_GETITEMRECT, 0, (LPARAM)&rc);
+ ok(!IsRectEmpty(&rc), "empty item rect\n");
+ ok(rc.top < 0, "rc.top is not negative (%d)\n", rc.top);
+
+ DestroyWindow(hLB);
+ DestroyWindow(parent);
+}
+
+#define listbox_test_query(exp, got) \
+ ok(exp.selected == got.selected, "expected selected %d, got %d\n", exp.selected, got.selected); \
+ ok(exp.anchor == got.anchor, "expected anchor %d, got %d\n", exp.anchor, got.anchor); \
+ ok(exp.caret == got.caret, "expected caret %d, got %d\n", exp.caret, got.caret); \
+ ok(exp.selcount == got.selcount, "expected selcount %d, got %d\n", exp.selcount, got.selcount);
+
+static void test_LB_SELITEMRANGE(void)
+{
+ static const struct listbox_stat test_nosel = { 0, LB_ERR, 0, 0 };
+ static const struct listbox_stat test_1 = { 0, LB_ERR, 0, 2 };
+ static const struct listbox_stat test_2 = { 0, LB_ERR, 0, 3 };
+ static const struct listbox_stat test_3 = { 0, LB_ERR, 0, 4 };
+ struct listbox_stat answer;
+ HWND hLB;
+ INT ret;
+
+ hLB = create_listbox(LBS_EXTENDEDSEL, 0);
+ assert(hLB);
+
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_nosel, answer);
+
+ ret = SendMessageA(hLB, LB_SELITEMRANGE, TRUE, MAKELPARAM(1, 2));
+ ok(ret == LB_OKAY, "LB_SELITEMRANGE returned %d instead of LB_OKAY\n", ret);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_1, answer);
+
+ SendMessageA(hLB, LB_SETSEL, FALSE, -1);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_nosel, answer);
+
+ ret = SendMessageA(hLB, LB_SELITEMRANGE, TRUE, MAKELPARAM(0, 4));
+ ok(ret == LB_OKAY, "LB_SELITEMRANGE returned %d instead of LB_OKAY\n", ret);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_3, answer);
+
+ SendMessageA(hLB, LB_SETSEL, FALSE, -1);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_nosel, answer);
+
+ ret = SendMessageA(hLB, LB_SELITEMRANGE, TRUE, MAKELPARAM(-5, 5));
+ ok(ret == LB_OKAY, "LB_SELITEMRANGE returned %d instead of LB_OKAY\n", ret);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_nosel, answer);
+
+ SendMessageA(hLB, LB_SETSEL, FALSE, -1);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_nosel, answer);
+
+ ret = SendMessageA(hLB, LB_SELITEMRANGE, TRUE, MAKELPARAM(2, 10));
+ ok(ret == LB_OKAY, "LB_SELITEMRANGE returned %d instead of LB_OKAY\n", ret);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_1, answer);
+
+ SendMessageA(hLB, LB_SETSEL, FALSE, -1);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_nosel, answer);
+
+ ret = SendMessageA(hLB, LB_SELITEMRANGE, TRUE, MAKELPARAM(4, 10));
+ ok(ret == LB_OKAY, "LB_SELITEMRANGE returned %d instead of LB_OKAY\n", ret);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_nosel, answer);
+
+ SendMessageA(hLB, LB_SETSEL, FALSE, -1);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_nosel, answer);
+
+ ret = SendMessageA(hLB, LB_SELITEMRANGE, TRUE, MAKELPARAM(10, 1));
+ ok(ret == LB_OKAY, "LB_SELITEMRANGE returned %d instead of LB_OKAY\n", ret);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_2, answer);
+
+ SendMessageA(hLB, LB_SETSEL, FALSE, -1);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_nosel, answer);
+
+ ret = SendMessageA(hLB, LB_SELITEMRANGE, TRUE, MAKELPARAM(1, -1));
+ ok(ret == LB_OKAY, "LB_SELITEMRANGE returned %d instead of LB_OKAY\n", ret);
+ listbox_query(hLB, &answer);
+ listbox_test_query(test_2, answer);
+
+ DestroyWindow(hLB);
+}
+
+static void test_LB_SETCURSEL(void)
+{
+ HWND parent, hLB;
+ INT ret;
+
+ parent = create_parent();
+ ok(parent != NULL, "Failed to create parent window.\n");
+
+ hLB = create_listbox(LBS_NOINTEGRALHEIGHT | WS_CHILD, parent);
+ ok(hLB != NULL, "Failed to create listbox.\n");
+
+ SendMessageA(hLB, LB_SETITEMHEIGHT, 0, 32);
+
+ ret = SendMessageA(hLB, LB_SETCURSEL, 2, 0);
+ ok(ret == 2, "LB_SETCURSEL returned %d instead of 2\n", ret);
+ ret = GetScrollPos(hLB, SB_VERT);
+ ok(ret == 0, "expected vscroll 0, got %d\n", ret);
+
+ ret = SendMessageA(hLB, LB_SETCURSEL, 3, 0);
+ ok(ret == 3, "LB_SETCURSEL returned %d instead of 3\n", ret);
+ ret = GetScrollPos(hLB, SB_VERT);
+ ok(ret == 1, "expected vscroll 1, got %d\n", ret);
+
+ DestroyWindow(hLB);
+}
+
+static void test_listbox_height(void)
+{
+ HWND hList;
+ int r, id;
+
+ hList = CreateWindowA( "ListBox", "list test", 0,
+ 1, 1, 600, 100, NULL, NULL, NULL, NULL );
+ ok( hList != NULL, "failed to create listbox\n");
+
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi");
+ ok( id == 0, "item id wrong\n");
+
+ r = SendMessageA( hList, LB_SETITEMHEIGHT, 0, MAKELPARAM( 20, 0 ));
+ ok( r == 0, "send message failed\n");
+
+ r = SendMessageA(hList, LB_GETITEMHEIGHT, 0, 0 );
+ ok( r == 20, "height wrong\n");
+
+ r = SendMessageA( hList, LB_SETITEMHEIGHT, 0, MAKELPARAM( 0, 30 ));
+ ok( r == -1, "send message failed\n");
+
+ r = SendMessageA(hList, LB_GETITEMHEIGHT, 0, 0 );
+ ok( r == 20, "height wrong\n");
+
+ r = SendMessageA( hList, LB_SETITEMHEIGHT, 0, MAKELPARAM( 256, 0 ));
+ ok( r == -1, "Failed to set item height, %d.\n", r);
+
+ r = SendMessageA(hList, LB_GETITEMHEIGHT, 0, 0 );
+ ok( r == 20, "Unexpected item height %d.\n", r);
+
+ r = SendMessageA( hList, LB_SETITEMHEIGHT, 0, MAKELPARAM( 0xff, 0 ));
+ ok( r == 0, "send message failed\n");
+
+ r = SendMessageA(hList, LB_GETITEMHEIGHT, 0, 0 );
+ ok( r == 0xff, "height wrong\n");
+
+ DestroyWindow( hList );
+}
+
+static void test_itemfrompoint(void)
+{
+ /* WS_POPUP is required in order to have a more accurate size calculation (
+ without caption). LBS_NOINTEGRALHEIGHT is required in order to test
+ behavior of partially-displayed item.
+ */
+ HWND hList = CreateWindowA( "ListBox", "list test",
+ WS_VISIBLE|WS_POPUP|LBS_NOINTEGRALHEIGHT,
+ 1, 1, 600, 100, NULL, NULL, NULL, NULL );
+ ULONG r, id;
+ RECT rc;
+
+ /* For an empty listbox win2k returns 0x1ffff, win98 returns 0x10000, nt4 returns 0xffffffff */
+ r = SendMessageA(hList, LB_ITEMFROMPOINT, 0, MAKELPARAM( /* x */ 30, /* y */ 30 ));
+ ok( r == 0x1ffff || r == 0x10000 || r == 0xffffffff, "ret %x\n", r );
+
+ r = SendMessageA(hList, LB_ITEMFROMPOINT, 0, MAKELPARAM( 700, 30 ));
+ ok( r == 0x1ffff || r == 0x10000 || r == 0xffffffff, "ret %x\n", r );
+
+ r = SendMessageA(hList, LB_ITEMFROMPOINT, 0, MAKELPARAM( 30, 300 ));
+ ok( r == 0x1ffff || r == 0x10000 || r == 0xffffffff, "ret %x\n", r );
+
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi");
+ ok( id == 0, "item id wrong\n");
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi1");
+ ok( id == 1, "item id wrong\n");
+
+ r = SendMessageA(hList, LB_ITEMFROMPOINT, 0, MAKELPARAM( /* x */ 30, /* y */ 30 ));
+ ok( r == 0x1, "ret %x\n", r );
+
+ r = SendMessageA(hList, LB_ITEMFROMPOINT, 0, MAKELPARAM( /* x */ 30, /* y */ 601 ));
+ ok( r == 0x10001, "ret %x\n", r );
+
+ /* Resize control so that below assertions about sizes are valid */
+ r = SendMessageA( hList, LB_GETITEMRECT, 0, (LPARAM)&rc);
+ ok( r == 1, "ret %x\n", r);
+ r = MoveWindow(hList, 1, 1, 600, (rc.bottom - rc.top + 1) * 9 / 2, TRUE);
+ ok( r != 0, "ret %x\n", r);
+
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi2");
+ ok( id == 2, "item id wrong\n");
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi3");
+ ok( id == 3, "item id wrong\n");
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi4");
+ ok( id == 4, "item id wrong\n");
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi5");
+ ok( id == 5, "item id wrong\n");
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi6");
+ ok( id == 6, "item id wrong\n");
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi7");
+ ok( id == 7, "item id wrong\n");
+
+ /* Set the listbox up so that id 1 is at the top, this leaves 5
+ partially visible at the bottom and 6, 7 are invisible */
+
+ SendMessageA( hList, LB_SETTOPINDEX, 1, 0);
+ r = SendMessageA( hList, LB_GETTOPINDEX, 0, 0);
+ ok( r == 1, "top %d\n", r);
+
+ r = SendMessageA( hList, LB_GETITEMRECT, 5, (LPARAM)&rc);
+ ok( r == 1, "ret %x\n", r);
+ r = SendMessageA( hList, LB_GETITEMRECT, 6, (LPARAM)&rc);
+ ok( r == 0, "ret %x\n", r);
+
+ r = SendMessageA( hList, LB_ITEMFROMPOINT, 0, MAKELPARAM(/* x */ 10, /* y */ 10) );
+ ok( r == 1, "ret %x\n", r);
+
+ r = SendMessageA( hList, LB_ITEMFROMPOINT, 0, MAKELPARAM(1000, 10) );
+ ok( r == 0x10001, "ret %x\n", r );
+
+ r = SendMessageA( hList, LB_ITEMFROMPOINT, 0, MAKELPARAM(10, -10) );
+ ok( r == 0x10001, "ret %x\n", r );
+
+ r = SendMessageA( hList, LB_ITEMFROMPOINT, 0, MAKELPARAM(10, 100) );
+ ok( r == 0x10005, "item %x\n", r );
+
+ r = SendMessageA( hList, LB_ITEMFROMPOINT, 0, MAKELPARAM(10, 200) );
+ ok( r == 0x10005, "item %x\n", r );
+
+ DestroyWindow( hList );
+}
+
+static void test_listbox_item_data(void)
+{
+ HWND hList;
+ int r, id;
+
+ hList = CreateWindowA( "ListBox", "list test", 0,
+ 1, 1, 600, 100, NULL, NULL, NULL, NULL );
+ ok( hList != NULL, "failed to create listbox\n");
+
+ id = SendMessageA( hList, LB_ADDSTRING, 0, (LPARAM) "hi");
+ ok( id == 0, "item id wrong\n");
+
+ r = SendMessageA( hList, LB_SETITEMDATA, 0, MAKELPARAM( 20, 0 ));
+ ok(r == TRUE, "LB_SETITEMDATA returned %d instead of TRUE\n", r);
+
+ r = SendMessageA( hList, LB_GETITEMDATA, 0, 0);
+ ok( r == 20, "get item data failed\n");
+
+ DestroyWindow( hList );
+}
+
+static void test_listbox_LB_DIR(void)
+{
+ HWND hList;
+ int res, itemCount;
+ int itemCount_justFiles;
+ int itemCount_justDrives;
+ int itemCount_allFiles;
+ int itemCount_allDirs;
+ int i;
+ char pathBuffer[MAX_PATH];
+ char * p;
+ char driveletter;
+ const char *wildcard = "*";
+ HANDLE file;
+
+ file = CreateFileA( "wtest1.tmp.c", GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL );
+ ok(file != INVALID_HANDLE_VALUE, "Error creating the test file: %d\n", GetLastError());
+ CloseHandle( file );
+
+ /* NOTE: for this test to succeed, there must be no subdirectories
+ under the current directory. In addition, there must be at least
+ one file that fits the wildcard w*.c . Normally, the test
+ directory itself satisfies both conditions.
+ */
+ hList = CreateWindowA( "ListBox", "list test", WS_VISIBLE|WS_POPUP,
+ 1, 1, 600, 100, NULL, NULL, NULL, NULL );
+ assert(hList);
+
+ /* Test for standard usage */
+
+ /* This should list all the files in the test directory. */
+ strcpy(pathBuffer, wildcard);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, 0, (LPARAM)pathBuffer);
+ if (res == -1) /* "*" wildcard doesn't work on win9x */
+ {
+ wildcard = "*.*";
+ strcpy(pathBuffer, wildcard);
+ res = SendMessageA(hList, LB_DIR, 0, (LPARAM)pathBuffer);
+ }
+ ok (res >= 0, "SendMessage(LB_DIR, 0, *) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount > 0, "SendMessage(LB_DIR) did NOT fill the listbox!\n");
+ itemCount_allFiles = itemCount;
+ ok(res + 1 == itemCount,
+ "SendMessage(LB_DIR, 0, *) returned incorrect index (expected %d got %d)!\n",
+ itemCount - 1, res);
+
+ /* This tests behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, 0, (LPARAM)pathBuffer);
+ ok (res == -1, "SendMessage(LB_DIR, 0, %s) returned %d, expected -1\n", BAD_EXTENSION, res);
+
+ /* There should be NO content in the listbox */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == 0, "SendMessage(LB_DIR) DID fill the listbox!\n");
+
+
+ /* This should list all the w*.c files in the test directory
+ * As of this writing, this includes win.c, winstation.c, wsprintf.c
+ */
+ strcpy(pathBuffer, "w*.c");
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, 0, (LPARAM)pathBuffer);
+ ok (res >= 0, "SendMessage(LB_DIR, 0, w*.c) failed - 0x%08x\n", GetLastError());
+
+ /* Path specification does NOT converted to uppercase */
+ ok (!strcmp(pathBuffer, "w*.c"),
+ "expected no change to pathBuffer, got %s\n", pathBuffer);
+
+ /* There should be some content in the listbox */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount > 0, "SendMessage(LB_DIR) did NOT fill the listbox!\n");
+ itemCount_justFiles = itemCount;
+ ok(res + 1 == itemCount,
+ "SendMessage(LB_DIR, 0, w*.c) returned incorrect index (expected %d got %d)!\n",
+ itemCount - 1, res);
+
+ /* Every single item in the control should start with a w and end in .c */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ SendMessageA(hList, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ p = pathBuffer + strlen(pathBuffer);
+ ok(((pathBuffer[0] == 'w' || pathBuffer[0] == 'W') &&
+ (*(p-1) == 'c' || *(p-1) == 'C') &&
+ (*(p-2) == '.')), "Element %d (%s) does not fit requested w*.c\n", i, pathBuffer);
+ }
+
+ /* Test DDL_DIRECTORY */
+ strcpy(pathBuffer, wildcard);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY, (LPARAM)pathBuffer);
+ ok (res > 0, "SendMessage(LB_DIR, DDL_DIRECTORY, *) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox.
+ * All files plus "[..]"
+ */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ itemCount_allDirs = itemCount - itemCount_allFiles;
+ ok (itemCount >= itemCount_allFiles,
+ "SendMessage(LB_DIR, DDL_DIRECTORY, *) filled with %d entries, expected > %d\n",
+ itemCount, itemCount_allFiles);
+ ok(res + 1 == itemCount,
+ "SendMessage(LB_DIR, DDL_DIRECTORY, *) returned incorrect index (expected %d got %d)!\n",
+ itemCount - 1, res);
+
+ /* This tests behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY, (LPARAM)pathBuffer);
+ ok (res == -1, "SendMessage(LB_DIR, DDL_DIRECTORY, %s) returned %d, expected -1\n", BAD_EXTENSION, res);
+
+ /* There should be NO content in the listbox */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == 0, "SendMessage(LB_DIR) DID fill the listbox!\n");
+
+ /* Test DDL_DIRECTORY */
+ strcpy(pathBuffer, "w*.c");
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY, (LPARAM)pathBuffer);
+ ok (res >= 0, "SendMessage(LB_DIR, DDL_DIRECTORY, w*.c) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox. Since the parent directory does not
+ * fit w*.c, there should be exactly the same number of items as without DDL_DIRECTORY
+ */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justFiles,
+ "SendMessage(LB_DIR, DDL_DIRECTORY, w*.c) filled with %d entries, expected %d\n",
+ itemCount, itemCount_justFiles);
+ ok(res + 1 == itemCount,
+ "SendMessage(LB_DIR, DDL_DIRECTORY, w*.c) returned incorrect index (expected %d got %d)!\n",
+ itemCount - 1, res);
+
+ /* Every single item in the control should start with a w and end in .c. */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ SendMessageA(hList, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ p = pathBuffer + strlen(pathBuffer);
+ ok(
+ ((pathBuffer[0] == 'w' || pathBuffer[0] == 'W') &&
+ (*(p-1) == 'c' || *(p-1) == 'C') &&
+ (*(p-2) == '.')), "Element %d (%s) does not fit requested w*.c\n", i, pathBuffer);
+ }
+
+ /* Test DDL_DRIVES|DDL_EXCLUSIVE */
+ strcpy(pathBuffer, wildcard);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DRIVES|DDL_EXCLUSIVE, (LPARAM)pathBuffer);
+ ok (res >= 0, "SendMessage(LB_DIR, DDL_DRIVES|DDL_EXCLUSIVE, *) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox. In particular, there should
+ * be at least one element before, since the string "[-c-]" should
+ * have been added. Depending on the user setting, more drives might have
+ * been added.
+ */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount >= 1,
+ "SendMessage(LB_DIR, DDL_DRIVES|DDL_EXCLUSIVE, *) filled with %d entries, expected at least %d\n",
+ itemCount, 1);
+ itemCount_justDrives = itemCount;
+ ok(res + 1 == itemCount, "SendMessage(LB_DIR, DDL_DRIVES|DDL_EXCLUSIVE, *) returned incorrect index!\n");
+
+ /* Every single item in the control should fit the format [-c-] */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(hList, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ ok( strlen(pathBuffer) == 5, "Length of drive string is not 5\n" );
+ ok( sscanf(pathBuffer, "[-%c-]", &driveletter) == 1, "Element %d (%s) does not fit [-X-]\n", i, pathBuffer);
+ ok( driveletter >= 'a' && driveletter <= 'z', "Drive letter not in range a..z, got ascii %d\n", driveletter);
+ if (!(driveletter >= 'a' && driveletter <= 'z'))
+ {
+ /* Correct after invalid entry is found */
+ itemCount_justDrives--;
+ }
+ }
+
+ /* This tests behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DRIVES|DDL_EXCLUSIVE, (LPARAM)pathBuffer);
+ ok (res == itemCount_justDrives -1, "SendMessage(LB_DIR, DDL_DRIVES|DDL_EXCLUSIVE, %s) returned %d, expected %d\n",
+ BAD_EXTENSION, res, itemCount_justDrives -1);
+
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justDrives, "SendMessage(LB_DIR) returned %d expected %d\n",
+ itemCount, itemCount_justDrives);
+
+ /* Test DDL_DRIVES. */
+ strcpy(pathBuffer, wildcard);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DRIVES, (LPARAM)pathBuffer);
+ ok (res > 0, "SendMessage(LB_DIR, DDL_DRIVES, *) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox. In particular, there should
+ * be at least one element before, since the string "[-c-]" should
+ * have been added. Depending on the user setting, more drives might have
+ * been added.
+ */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justDrives + itemCount_allFiles,
+ "SendMessage(LB_DIR, DDL_DRIVES, *) filled with %d entries, expected %d\n",
+ itemCount, itemCount_justDrives + itemCount_allFiles);
+ ok(res + 1 == itemCount, "SendMessage(LB_DIR, DDL_DRIVES, *) returned incorrect index!\n");
+
+ /* This tests behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DRIVES, (LPARAM)pathBuffer);
+ ok (res == itemCount_justDrives -1, "SendMessage(LB_DIR, DDL_DRIVES, %s) returned %d, expected %d\n",
+ BAD_EXTENSION, res, itemCount_justDrives -1);
+
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == res + 1, "SendMessage(LB_DIR) returned %d expected %d\n", itemCount, res + 1);
+
+ /* Test DDL_DRIVES. */
+ strcpy(pathBuffer, "w*.c");
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DRIVES, (LPARAM)pathBuffer);
+ ok (res > 0, "SendMessage(LB_DIR, DDL_DRIVES, w*.c) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox. In particular, there should
+ * be at least one element before, since the string "[-c-]" should
+ * have been added. Depending on the user setting, more drives might have
+ * been added.
+ */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justDrives + itemCount_justFiles,
+ "SendMessage(LB_DIR, DDL_DRIVES, w*.c) filled with %d entries, expected %d\n",
+ itemCount, itemCount_justDrives + itemCount_justFiles);
+ ok(res + 1 == itemCount, "SendMessage(LB_DIR, DDL_DRIVES, w*.c) returned incorrect index!\n");
+
+ /* Every single item in the control should fit the format [-c-], or w*.c */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(hList, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ p = pathBuffer + strlen(pathBuffer);
+ if (sscanf(pathBuffer, "[-%c-]", &driveletter) == 1)
+ {
+ ok( strlen(pathBuffer) == 5, "Length of drive string is not 5\n" );
+ ok( driveletter >= 'a' && driveletter <= 'z', "Drive letter not in range a..z, got ascii %d\n", driveletter);
+ }
+ else
+ {
+ ok(
+ ((pathBuffer[0] == 'w' || pathBuffer[0] == 'W') &&
+ (*(p-1) == 'c' || *(p-1) == 'C') &&
+ (*(p-2) == '.')), "Element %d (%s) does not fit requested w*.c\n", i, pathBuffer);
+ }
+ }
+
+ /* Test DDL_DIRECTORY|DDL_DRIVES. This does *not* imply DDL_EXCLUSIVE */
+ strcpy(pathBuffer, wildcard);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY|DDL_DRIVES, (LPARAM)pathBuffer);
+ ok (res > 0, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES, *) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox. In particular, there should
+ * be exactly the number of plain files, plus the number of mapped drives.
+ */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_allFiles + itemCount_justDrives + itemCount_allDirs,
+ "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES) filled with %d entries, expected %d\n",
+ itemCount, itemCount_allFiles + itemCount_justDrives + itemCount_allDirs);
+ ok(res + 1 == itemCount, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES, w*.c) returned incorrect index!\n");
+
+ /* Every single item in the control should start with a w and end in .c,
+ * except for the "[..]" string, which should appear exactly as it is,
+ * and the mapped drives in the format "[-X-]".
+ */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(hList, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ if (sscanf(pathBuffer, "[-%c-]", &driveletter) == 1)
+ ok( driveletter >= 'a' && driveletter <= 'z', "Drive letter not in range a..z, got ascii %d\n", driveletter);
+ }
+
+ /* This tests behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY|DDL_DRIVES, (LPARAM)pathBuffer);
+ ok (res == itemCount_justDrives -1, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES, %s) returned %d, expected %d\n",
+ BAD_EXTENSION, res, itemCount_justDrives -1);
+
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == res + 1, "SendMessage(LB_DIR) returned %d expected %d\n", itemCount, res + 1);
+
+ /* Test DDL_DIRECTORY|DDL_DRIVES. */
+ strcpy(pathBuffer, "w*.c");
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY|DDL_DRIVES, (LPARAM)pathBuffer);
+ ok (res > 0, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES, w*.c) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox. In particular, there should
+ * be exactly the number of plain files, plus the number of mapped drives.
+ */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justFiles + itemCount_justDrives,
+ "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES) filled with %d entries, expected %d\n",
+ itemCount, itemCount_justFiles + itemCount_justDrives);
+ ok(res + 1 == itemCount, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES, w*.c) returned incorrect index!\n");
+
+ /* Every single item in the control should start with a w and end in .c,
+ * except the mapped drives in the format "[-X-]". The "[..]" directory
+ * should not appear.
+ */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(hList, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ p = pathBuffer + strlen(pathBuffer);
+ if (sscanf(pathBuffer, "[-%c-]", &driveletter) == 1)
+ ok( driveletter >= 'a' && driveletter <= 'z', "Drive letter not in range a..z, got ascii %d\n", driveletter);
+ else
+ ok(
+ ((pathBuffer[0] == 'w' || pathBuffer[0] == 'W') &&
+ (*(p-1) == 'c' || *(p-1) == 'C') &&
+ (*(p-2) == '.')), "Element %d (%s) does not fit requested w*.c\n", i, pathBuffer);
+ }
+
+ /* Test DDL_DIRECTORY|DDL_EXCLUSIVE. */
+ strcpy(pathBuffer, wildcard);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY|DDL_EXCLUSIVE, (LPARAM)pathBuffer);
+ ok (res != -1 || broken(res == -1), "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_EXCLUSIVE, *) failed err %u\n",
+ GetLastError());
+
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_allDirs,
+ "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_EXCLUSIVE) filled with %d entries, expected %d\n",
+ itemCount, itemCount_allDirs);
+ ok(res + 1 == itemCount, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_EXCLUSIVE, *) returned incorrect index!\n");
+
+ if (itemCount && GetCurrentDirectoryA( MAX_PATH, pathBuffer ) > 3) /* there's no [..] in drive root */
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ SendMessageA(hList, LB_GETTEXT, 0, (LPARAM)pathBuffer);
+ ok( !strcmp(pathBuffer, "[..]"), "First element is not [..]\n");
+ }
+
+ /* This tests behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY|DDL_EXCLUSIVE, (LPARAM)pathBuffer);
+ ok (res == -1, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_EXCLUSIVE, %s) returned %d, expected %d\n",
+ BAD_EXTENSION, res, -1);
+
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == res + 1, "SendMessage(LB_DIR) returned %d expected %d\n", itemCount, res + 1);
+
+
+ /* Test DDL_DIRECTORY|DDL_EXCLUSIVE. */
+ strcpy(pathBuffer, "w*.c");
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY|DDL_EXCLUSIVE, (LPARAM)pathBuffer);
+ ok (res == LB_ERR, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_EXCLUSIVE, w*.c) returned %d expected %d\n", res, LB_ERR);
+
+ /* There should be no elements, since "[..]" does not fit w*.c */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == 0,
+ "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_EXCLUSIVE) filled with %d entries, expected %d\n",
+ itemCount, 0);
+
+ /* Test DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE. */
+ strcpy(pathBuffer, wildcard);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE, (LPARAM)pathBuffer);
+ ok (res > 0, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE, w*.c,) failed - 0x%08x\n", GetLastError());
+
+ /* There should be no plain files on the listbox */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justDrives + itemCount_allDirs,
+ "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE) filled with %d entries, expected %d\n",
+ itemCount, itemCount_justDrives + itemCount_allDirs);
+ ok(res + 1 == itemCount, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE, w*.c) returned incorrect index!\n");
+
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(hList, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ if (sscanf(pathBuffer, "[-%c-]", &driveletter) == 1)
+ ok( driveletter >= 'a' && driveletter <= 'z', "Drive letter not in range a..z, got ascii %d\n", driveletter);
+ else
+ ok( pathBuffer[0] == '[' && pathBuffer[strlen(pathBuffer)-1] == ']',
+ "Element %d (%s) does not fit expected [...]\n", i, pathBuffer);
+ }
+
+ /* This tests behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE, (LPARAM)pathBuffer);
+ ok (res == itemCount_justDrives -1, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE, %s) returned %d, expected %d\n",
+ BAD_EXTENSION, res, itemCount_justDrives -1);
+
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == res + 1, "SendMessage(LB_DIR) returned %d expected %d\n", itemCount, res + 1);
+
+ /* Test DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE. */
+ strcpy(pathBuffer, "w*.c");
+ SendMessageA(hList, LB_RESETCONTENT, 0, 0);
+ res = SendMessageA(hList, LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE, (LPARAM)pathBuffer);
+ ok (res >= 0, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE, w*.c,) failed - 0x%08x\n", GetLastError());
+
+ /* There should be no plain files on the listbox, and no [..], since it does not fit w*.c */
+ itemCount = SendMessageA(hList, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justDrives,
+ "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE) filled with %d entries, expected %d\n",
+ itemCount, itemCount_justDrives);
+ ok(res + 1 == itemCount, "SendMessage(LB_DIR, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE, w*.c) returned incorrect index!\n");
+
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(hList, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ ok (sscanf(pathBuffer, "[-%c-]", &driveletter) == 1, "Element %d (%s) does not fit [-X-]\n", i, pathBuffer);
+ ok( driveletter >= 'a' && driveletter <= 'z', "Drive letter not in range a..z, got ascii %d\n", driveletter);
+ }
+ DestroyWindow(hList);
+
+ DeleteFileA( "wtest1.tmp.c" );
+}
+
+static HWND g_listBox;
+static HWND g_label;
+
+#define ID_TEST_LABEL 1001
+#define ID_TEST_LISTBOX 1002
+
+static BOOL on_listbox_container_create(HWND hwnd, CREATESTRUCTA *lpcs)
+{
+ g_label = CreateWindowA("Static", "Contents of static control before DlgDirList.",
+ WS_CHILD | WS_VISIBLE, 10, 10, 512, 32, hwnd, (HMENU)ID_TEST_LABEL, NULL, 0);
+ if (!g_label) return FALSE;
+
+ g_listBox = CreateWindowA("ListBox", "DlgDirList test",
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER | WS_VSCROLL, 10, 60, 256, 256,
+ hwnd, (HMENU)ID_TEST_LISTBOX, NULL, 0);
+ if (!g_listBox) return FALSE;
+
+ return TRUE;
+}
+
+static LRESULT CALLBACK listbox_container_window_procA(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT result = 0;
+
+ switch (uiMsg)
+ {
+ case WM_DESTROY:
+ PostQuitMessage(0);
+ break;
+ case WM_CREATE:
+ result = on_listbox_container_create(hwnd, (CREATESTRUCTA *)lParam) ? 0 : (LRESULT)-1;
+ break;
+ default:
+ result = DefWindowProcA(hwnd, uiMsg, wParam, lParam);
+ break;
+ }
+ return result;
+}
+
+static BOOL RegisterListboxWindowClass(HINSTANCE hInst)
+{
+ WNDCLASSA cls;
+
+ cls.style = 0;
+ cls.cbClsExtra = 0;
+ cls.cbWndExtra = 0;
+ cls.hInstance = hInst;
+ cls.hIcon = NULL;
+ cls.hCursor = LoadCursorA (NULL, (LPCSTR)IDC_ARROW);
+ cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
+ cls.lpszMenuName = NULL;
+ cls.lpfnWndProc = listbox_container_window_procA;
+ cls.lpszClassName = "ListboxContainerClass";
+ if (!RegisterClassA (&cls)) return FALSE;
+
+ return TRUE;
+}
+
+static void test_listbox_dlgdir(void)
+{
+ HINSTANCE hInst;
+ HWND hWnd;
+ int res, itemCount;
+ int itemCount_allDirs;
+ int itemCount_justFiles;
+ int itemCount_justDrives;
+ int i;
+ char pathBuffer[MAX_PATH];
+ char itemBuffer[MAX_PATH];
+ char tempBuffer[MAX_PATH];
+ char * p;
+ char driveletter;
+ HANDLE file;
+
+ file = CreateFileA( "wtest1.tmp.c", GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL );
+ ok(file != INVALID_HANDLE_VALUE, "Error creating the test file: %d\n", GetLastError());
+ CloseHandle( file );
+
+ /* NOTE: for this test to succeed, there must be no subdirectories
+ under the current directory. In addition, there must be at least
+ one file that fits the wildcard w*.c . Normally, the test
+ directory itself satisfies both conditions.
+ */
+
+ hInst = GetModuleHandleA(0);
+ if (!RegisterListboxWindowClass(hInst)) assert(0);
+
+ hWnd = CreateWindowA("ListboxContainerClass", "ListboxContainerClass",
+ WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+ CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+ NULL, NULL, hInst, 0);
+ ok(hWnd != NULL, "Failed to create container window.\n");
+
+ /* Test for standard usage */
+
+ /* The following should be overwritten by the directory path */
+ SendMessageA(g_label, WM_SETTEXT, 0, (LPARAM)"default contents");
+
+ /* This should list all the w*.c files in the test directory
+ * As of this writing, this includes win.c, winstation.c, wsprintf.c
+ */
+ strcpy(pathBuffer, "w*.c");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, 0);
+ ok (res == 1, "DlgDirList(*.c, 0) returned %d - expected 1 - 0x%08x\n", res, GetLastError());
+
+ /* Path specification gets converted to uppercase */
+ ok (!strcmp(pathBuffer, "W*.C"),
+ "expected conversion to uppercase, got %s\n", pathBuffer);
+
+ /* Loaded path should have overwritten the label text */
+ SendMessageA(g_label, WM_GETTEXT, MAX_PATH, (LPARAM)pathBuffer);
+ ok (strcmp("default contents", pathBuffer), "DlgDirList() did not modify static control!\n");
+
+ /* There should be some content in the listbox */
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount > 0, "DlgDirList() did NOT fill the listbox!\n");
+ itemCount_justFiles = itemCount;
+
+ /* Every single item in the control should start with a w and end in .c */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ SendMessageA(g_listBox, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ p = pathBuffer + strlen(pathBuffer);
+ ok(((pathBuffer[0] == 'w' || pathBuffer[0] == 'W') &&
+ (*(p-1) == 'c' || *(p-1) == 'C') &&
+ (*(p-2) == '.')), "Element %d (%s) does not fit requested w*.c\n", i, pathBuffer);
+ }
+
+ /* Test behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, 0);
+ ok (res == 1, "DlgDirList(%s, 0) returned %d expected 1\n", BAD_EXTENSION, res);
+
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount == 0, "DlgDirList() DID fill the listbox!\n");
+
+ /* Test DDL_DIRECTORY */
+ strcpy(pathBuffer, "w*.c");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DIRECTORY);
+ ok (res == 1, "DlgDirList(*.c, DDL_DIRECTORY) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox. In particular, there should
+ * be exactly more elements than before, since the directories should
+ * have been added.
+ */
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ itemCount_allDirs = itemCount - itemCount_justFiles;
+ ok (itemCount >= itemCount_justFiles, "DlgDirList(DDL_DIRECTORY) filled with %d entries, expected > %d\n",
+ itemCount, itemCount_justFiles);
+
+ /* Every single item in the control should start with a w and end in .c,
+ * except for the "[..]" string, which should appear exactly as it is.
+ */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ SendMessageA(g_listBox, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ p = pathBuffer + strlen(pathBuffer);
+ ok( (pathBuffer[0] == '[' && pathBuffer[strlen(pathBuffer)-1] == ']') ||
+ ((pathBuffer[0] == 'w' || pathBuffer[0] == 'W') &&
+ (*(p-1) == 'c' || *(p-1) == 'C') &&
+ (*(p-2) == '.')), "Element %d (%s) does not fit requested w*.c\n", i, pathBuffer);
+ }
+
+ /* Test behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DIRECTORY);
+ ok (res == 1, "DlgDirList(%s, DDL_DIRECTORY) returned %d expected 1\n", BAD_EXTENSION, res);
+
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_allDirs, "DlgDirList() incorrectly filled the listbox! (expected %d got %d)\n",
+ itemCount_allDirs, itemCount);
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ SendMessageA(g_listBox, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ ok( pathBuffer[0] == '[' && pathBuffer[strlen(pathBuffer)-1] == ']',
+ "Element %d (%s) does not fit requested [...]\n", i, pathBuffer);
+ }
+
+ /* Test DDL_DRIVES. At least on WinXP-SP2, this implies DDL_EXCLUSIVE */
+ strcpy(pathBuffer, "w*.c");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DRIVES);
+ ok (res == 1, "DlgDirList(*.c, DDL_DRIVES) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox. In particular, there should
+ * be at least one element before, since the string "[-c-]" should
+ * have been added. Depending on the user setting, more drives might have
+ * been added.
+ */
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount >= 1,
+ "DlgDirList(DDL_DRIVES) filled with %d entries, expected at least %d\n",
+ itemCount, 1);
+ itemCount_justDrives = itemCount;
+
+ /* Every single item in the control should fit the format [-c-] */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(g_listBox, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ ok( strlen(pathBuffer) == 5, "Length of drive string is not 5\n" );
+ ok( sscanf(pathBuffer, "[-%c-]", &driveletter) == 1, "Element %d (%s) does not fit [-X-]\n", i, pathBuffer);
+ ok( driveletter >= 'a' && driveletter <= 'z', "Drive letter not in range a..z, got ascii %d\n", driveletter);
+ if (!(driveletter >= 'a' && driveletter <= 'z')) {
+ /* Correct after invalid entry is found */
+ trace("removing count of invalid entry %s\n", pathBuffer);
+ itemCount_justDrives--;
+ }
+ }
+
+ /* Test behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DRIVES);
+ ok (res == 1, "DlgDirList(%s, DDL_DRIVES) returned %d expected 1\n", BAD_EXTENSION, res);
+
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justDrives, "DlgDirList() incorrectly filled the listbox!\n");
+
+ /* Test DDL_DIRECTORY|DDL_DRIVES. This does *not* imply DDL_EXCLUSIVE */
+ strcpy(pathBuffer, "w*.c");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DIRECTORY|DDL_DRIVES);
+ ok (res == 1, "DlgDirList(*.c, DDL_DIRECTORY|DDL_DRIVES) failed - 0x%08x\n", GetLastError());
+
+ /* There should be some content in the listbox. In particular, there should
+ * be exactly the number of plain files, plus the number of mapped drives,
+ * plus one "[..]"
+ */
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justFiles + itemCount_justDrives + itemCount_allDirs,
+ "DlgDirList(DDL_DIRECTORY|DDL_DRIVES) filled with %d entries, expected %d\n",
+ itemCount, itemCount_justFiles + itemCount_justDrives + itemCount_allDirs);
+
+ /* Every single item in the control should start with a w and end in .c,
+ * except for the "[..]" string, which should appear exactly as it is,
+ * and the mapped drives in the format "[-X-]".
+ */
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(g_listBox, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ p = pathBuffer + strlen(pathBuffer);
+ if (sscanf(pathBuffer, "[-%c-]", &driveletter) == 1)
+ ok( driveletter >= 'a' && driveletter <= 'z', "Drive letter not in range a..z, got ascii %d\n", driveletter);
+ else
+ ok( (pathBuffer[0] == '[' && pathBuffer[strlen(pathBuffer)-1] == ']') ||
+ ((pathBuffer[0] == 'w' || pathBuffer[0] == 'W') &&
+ (*(p-1) == 'c' || *(p-1) == 'C') &&
+ (*(p-2) == '.')), "Element %d (%s) does not fit requested w*.c\n", i, pathBuffer);
+ }
+
+ /* Test behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DIRECTORY|DDL_DRIVES);
+ ok (res == 1, "DlgDirList(%s, DDL_DIRECTORY|DDL_DRIVES) returned %d expected 1\n", BAD_EXTENSION, res);
+
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justDrives + itemCount_allDirs,
+ "DlgDirList() incorrectly filled the listbox! (expected %d got %d)\n",
+ itemCount_justDrives + itemCount_allDirs, itemCount);
+
+ /* Test DDL_DIRECTORY|DDL_EXCLUSIVE. */
+ strcpy(pathBuffer, "w*.c");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DIRECTORY|DDL_EXCLUSIVE);
+ ok (res == 1, "DlgDirList(*.c, DDL_DIRECTORY|DDL_EXCLUSIVE) failed - 0x%08x\n", GetLastError());
+
+ /* There should be exactly one element: "[..]" */
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_allDirs,
+ "DlgDirList(DDL_DIRECTORY|DDL_EXCLUSIVE) filled with %d entries, expected %d\n",
+ itemCount, itemCount_allDirs);
+
+ if (itemCount && GetCurrentDirectoryA( MAX_PATH, pathBuffer ) > 3) /* there's no [..] in drive root */
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ SendMessageA(g_listBox, LB_GETTEXT, 0, (LPARAM)pathBuffer);
+ ok( !strcmp(pathBuffer, "[..]"), "First (and only) element is not [..]\n");
+ }
+
+ /* Test behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DIRECTORY|DDL_EXCLUSIVE);
+ ok (res == 1, "DlgDirList(%s, DDL_DIRECTORY|DDL_EXCLUSIVE) returned %d expected 1\n", BAD_EXTENSION, res);
+
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_allDirs, "DlgDirList() incorrectly filled the listbox!\n");
+
+ /* Test DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE. */
+ strcpy(pathBuffer, "w*.c");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE);
+ ok (res == 1, "DlgDirList(*.c, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE) failed - 0x%08x\n", GetLastError());
+
+ /* There should be no plain files on the listbox */
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justDrives + itemCount_allDirs,
+ "DlgDirList(DDL_DIRECTORY|DDL_EXCLUSIVE) filled with %d entries, expected %d\n",
+ itemCount, itemCount_justDrives + itemCount_allDirs);
+
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(pathBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(g_listBox, LB_GETTEXT, i, (LPARAM)pathBuffer);
+ if (sscanf(pathBuffer, "[-%c-]", &driveletter) == 1)
+ ok( driveletter >= 'a' && driveletter <= 'z', "Drive letter not in range a..z, got ascii %d\n", driveletter);
+ else
+ ok( pathBuffer[0] == '[' && pathBuffer[strlen(pathBuffer)-1] == ']',
+ "Element %d (%s) does not fit expected [...]\n", i, pathBuffer);
+ }
+
+ /* Test behavior when no files match the wildcard */
+ strcpy(pathBuffer, BAD_EXTENSION);
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE);
+ ok (res == 1, "DlgDirList(%s, DDL_DIRECTORY|DDL_DRIVES|DDL_EXCLUSIVE) returned %d expected 1\n", BAD_EXTENSION, res);
+
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ ok (itemCount == itemCount_justDrives + itemCount_allDirs, "DlgDirList() incorrectly filled the listbox!\n");
+
+ /* Now test DlgDirSelectEx() in normal operation */
+ /* Fill with everything - drives, directory and all plain files. */
+ strcpy(pathBuffer, "*");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, ID_TEST_LABEL, DDL_DIRECTORY|DDL_DRIVES);
+ ok (res != 0, "DlgDirList(*, DDL_DIRECTORY|DDL_DRIVES) failed - 0x%08x\n", GetLastError());
+
+ SendMessageA(g_listBox, LB_SETCURSEL, -1, 0); /* Unselect any current selection */
+ memset(pathBuffer, 0, MAX_PATH);
+ SetLastError(0xdeadbeef);
+ res = DlgDirSelectExA(hWnd, pathBuffer, MAX_PATH, ID_TEST_LISTBOX);
+ ok (GetLastError() == 0xdeadbeef,
+ "DlgDirSelectEx() with no selection modified last error code from 0xdeadbeef to 0x%08x\n",
+ GetLastError());
+ ok (res == 0, "DlgDirSelectEx() with no selection returned %d, expected 0\n", res);
+ /* WinXP-SP2 leaves pathBuffer untouched, but Win98 fills it with garbage. */
+ /*
+ ok (strlen(pathBuffer) == 0, "DlgDirSelectEx() with no selection filled buffer with %s\n", pathBuffer);
+ */
+ /* Test proper drive/dir/file recognition */
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(itemBuffer, 0, MAX_PATH);
+ memset(pathBuffer, 0, MAX_PATH);
+ memset(tempBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(g_listBox, LB_GETTEXT, i, (LPARAM)itemBuffer);
+ res = SendMessageA(g_listBox, LB_SETCURSEL, i, 0);
+ ok (res == i, "SendMessageA(LB_SETCURSEL, %d) failed\n", i);
+ if (sscanf(itemBuffer, "[-%c-]", &driveletter) == 1)
+ {
+ /* Current item is a drive letter */
+ SetLastError(0xdeadbeef);
+ res = DlgDirSelectExA(hWnd, pathBuffer, MAX_PATH, ID_TEST_LISTBOX);
+ ok (GetLastError() == 0xdeadbeef,
+ "DlgDirSelectEx() with selection at %d modified last error code from 0xdeadbeef to 0x%08x\n",
+ i, GetLastError());
+ ok(res == 1, "DlgDirSelectEx() thinks %s (%s) is not a drive/directory!\n", itemBuffer, pathBuffer);
+
+ /* For drive letters, DlgDirSelectEx tacks on a colon */
+ ok (pathBuffer[0] == driveletter && pathBuffer[1] == ':' && pathBuffer[2] == '\0',
+ "%d: got \"%s\" expected \"%c:\"\n", i, pathBuffer, driveletter);
+ }
+ else if (itemBuffer[0] == '[')
+ {
+ /* Current item is the parent directory */
+ SetLastError(0xdeadbeef);
+ res = DlgDirSelectExA(hWnd, pathBuffer, MAX_PATH, ID_TEST_LISTBOX);
+ ok (GetLastError() == 0xdeadbeef,
+ "DlgDirSelectEx() with selection at %d modified last error code from 0xdeadbeef to 0x%08x\n",
+ i, GetLastError());
+ ok(res == 1, "DlgDirSelectEx() thinks %s (%s) is not a drive/directory!\n", itemBuffer, pathBuffer);
+
+ /* For directories, DlgDirSelectEx tacks on a backslash */
+ p = pathBuffer + strlen(pathBuffer);
+ ok (*(p-1) == '\\', "DlgDirSelectEx did NOT tack on a backslash to dir, got %s\n", pathBuffer);
+
+ tempBuffer[0] = '[';
+ lstrcpynA(tempBuffer + 1, pathBuffer, strlen(pathBuffer));
+ strcat(tempBuffer, "]");
+ ok (!strcmp(tempBuffer, itemBuffer), "Formatted directory should be %s, got %s\n", tempBuffer, itemBuffer);
+ }
+ else
+ {
+ /* Current item is a plain file */
+ SetLastError(0xdeadbeef);
+ res = DlgDirSelectExA(hWnd, pathBuffer, MAX_PATH, ID_TEST_LISTBOX);
+ ok (GetLastError() == 0xdeadbeef,
+ "DlgDirSelectEx() with selection at %d modified last error code from 0xdeadbeef to 0x%08x\n",
+ i, GetLastError());
+ ok(res == 0, "DlgDirSelectEx() thinks %s (%s) is a drive/directory!\n", itemBuffer, pathBuffer);
+
+ /* NOTE: WinXP tacks a period on all files that lack an extension. This affects
+ * for example, "Makefile", which gets reported as "Makefile."
+ */
+ strcpy(tempBuffer, itemBuffer);
+ if (strchr(tempBuffer, '.') == NULL) strcat(tempBuffer, ".");
+ ok (!strcmp(pathBuffer, tempBuffer), "Formatted file should be %s, got %s\n", tempBuffer, pathBuffer);
+ }
+ }
+
+ DeleteFileA( "wtest1.tmp.c" );
+
+ /* Now test DlgDirSelectEx() in abnormal operation */
+ /* Fill list with bogus entries, that look somewhat valid */
+ SendMessageA(g_listBox, LB_RESETCONTENT, 0, 0);
+ SendMessageA(g_listBox, LB_ADDSTRING, 0, (LPARAM)"[notexist.dir]");
+ SendMessageA(g_listBox, LB_ADDSTRING, 0, (LPARAM)"notexist.fil");
+ itemCount = SendMessageA(g_listBox, LB_GETCOUNT, 0, 0);
+ for (i = 0; i < itemCount; i++)
+ {
+ memset(itemBuffer, 0, MAX_PATH);
+ memset(pathBuffer, 0, MAX_PATH);
+ memset(tempBuffer, 0, MAX_PATH);
+ driveletter = '\0';
+ SendMessageA(g_listBox, LB_GETTEXT, i, (LPARAM)itemBuffer);
+ res = SendMessageA(g_listBox, LB_SETCURSEL, i, 0);
+ ok (res == i, "SendMessage(LB_SETCURSEL, %d) failed\n", i);
+ if (sscanf(itemBuffer, "[-%c-]", &driveletter) == 1)
+ {
+ /* Current item is a drive letter */
+ SetLastError(0xdeadbeef);
+ res = DlgDirSelectExA(hWnd, pathBuffer, MAX_PATH, ID_TEST_LISTBOX);
+ ok (GetLastError() == 0xdeadbeef,
+ "DlgDirSelectEx() with selection at %d modified last error code from 0xdeadbeef to 0x%08x\n",
+ i, GetLastError());
+ ok(res == 1, "DlgDirSelectEx() thinks %s (%s) is not a drive/directory!\n", itemBuffer, pathBuffer);
+
+ /* For drive letters, DlgDirSelectEx tacks on a colon */
+ ok (pathBuffer[0] == driveletter && pathBuffer[1] == ':' && pathBuffer[2] == '\0',
+ "%d: got \"%s\" expected \"%c:\"\n", i, pathBuffer, driveletter);
+ }
+ else if (itemBuffer[0] == '[')
+ {
+ /* Current item is the parent directory */
+ SetLastError(0xdeadbeef);
+ res = DlgDirSelectExA(hWnd, pathBuffer, MAX_PATH, ID_TEST_LISTBOX);
+ ok (GetLastError() == 0xdeadbeef,
+ "DlgDirSelectEx() with selection at %d modified last error code from 0xdeadbeef to 0x%08x\n",
+ i, GetLastError());
+ ok(res == 1, "DlgDirSelectEx() thinks %s (%s) is not a drive/directory!\n", itemBuffer, pathBuffer);
+
+ /* For directories, DlgDirSelectEx tacks on a backslash */
+ p = pathBuffer + strlen(pathBuffer);
+ ok (*(p-1) == '\\', "DlgDirSelectEx did NOT tack on a backslash to dir, got %s\n", pathBuffer);
+
+ tempBuffer[0] = '[';
+ lstrcpynA(tempBuffer + 1, pathBuffer, strlen(pathBuffer));
+ strcat(tempBuffer, "]");
+ ok (!strcmp(tempBuffer, itemBuffer), "Formatted directory should be %s, got %s\n", tempBuffer, itemBuffer);
+ }
+ else
+ {
+ /* Current item is a plain file */
+ SetLastError(0xdeadbeef);
+ res = DlgDirSelectExA(hWnd, pathBuffer, MAX_PATH, ID_TEST_LISTBOX);
+ ok (GetLastError() == 0xdeadbeef,
+ "DlgDirSelectEx() with selection at %d modified last error code from 0xdeadbeef to 0x%08x\n",
+ i, GetLastError());
+ ok(res == 0, "DlgDirSelectEx() thinks %s (%s) is a drive/directory!\n", itemBuffer, pathBuffer);
+
+ /* NOTE: WinXP and Win98 tack a period on all files that lack an extension.
+ * This affects for example, "Makefile", which gets reported as "Makefile."
+ */
+ strcpy(tempBuffer, itemBuffer);
+ if (strchr(tempBuffer, '.') == NULL) strcat(tempBuffer, ".");
+ ok (!strcmp(pathBuffer, tempBuffer), "Formatted file should be %s, got %s\n", tempBuffer, pathBuffer);
+ }
+ }
+
+ /* Test behavior when loading folders from root with and without wildcard */
+ strcpy(pathBuffer, "C:\\");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, 0, DDL_DIRECTORY | DDL_EXCLUSIVE);
+ ok(res, "DlgDirList failed to list C:\\ folders\n");
+ todo_wine ok(!strcmp(pathBuffer, "*"), "DlgDirList set the invalid path spec '%s', expected '*'\n", pathBuffer);
+
+ strcpy(pathBuffer, "C:\\*");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, 0, DDL_DIRECTORY | DDL_EXCLUSIVE);
+ ok(res, "DlgDirList failed to list C:\\* folders\n");
+ ok(!strcmp(pathBuffer, "*"), "DlgDirList set the invalid path spec '%s', expected '*'\n", pathBuffer);
+
+ /* Try loading files from an invalid folder */
+ SetLastError(0xdeadbeef);
+ strcpy(pathBuffer, "C:\\INVALID$$DIR");
+ res = DlgDirListA(hWnd, pathBuffer, ID_TEST_LISTBOX, 0, DDL_DIRECTORY | DDL_EXCLUSIVE);
+ todo_wine ok(!res, "DlgDirList should have failed with 0 but %d was returned\n", res);
+ todo_wine ok(GetLastError() == ERROR_NO_WILDCARD_CHARACTERS,
+ "GetLastError should return 0x589, got 0x%X\n",GetLastError());
+
+ DestroyWindow(hWnd);
+}
+
+static void test_set_count( void )
+{
+ HWND parent, listbox;
+ LONG ret;
+ RECT r;
+
+ parent = create_parent();
+ listbox = create_listbox( LBS_OWNERDRAWFIXED | LBS_NODATA | WS_CHILD | WS_VISIBLE, parent );
+
+ UpdateWindow( listbox );
+ GetUpdateRect( listbox, &r, TRUE );
+ ok( IsRectEmpty( &r ), "got non-empty rect\n");
+
+ ret = SendMessageA( listbox, LB_SETCOUNT, 100, 0 );
+ ok( ret == 0, "got %d\n", ret );
+ ret = SendMessageA( listbox, LB_GETCOUNT, 0, 0 );
+ ok( ret == 100, "got %d\n", ret );
+
+ GetUpdateRect( listbox, &r, TRUE );
+ ok( !IsRectEmpty( &r ), "got empty rect\n");
+
+ ValidateRect( listbox, NULL );
+ GetUpdateRect( listbox, &r, TRUE );
+ ok( IsRectEmpty( &r ), "got non-empty rect\n");
+
+ ret = SendMessageA( listbox, LB_SETCOUNT, 99, 0 );
+ ok( ret == 0, "got %d\n", ret );
+
+ GetUpdateRect( listbox, &r, TRUE );
+ ok( !IsRectEmpty( &r ), "got empty rect\n");
+
+ DestroyWindow( listbox );
+ DestroyWindow( parent );
+}
+
+static int lb_getlistboxinfo;
+
+static LRESULT WINAPI listbox_subclass_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ WNDPROC oldproc = (WNDPROC)GetWindowLongPtrA(hwnd, GWLP_USERDATA);
+
+ if (message == LB_GETLISTBOXINFO)
+ lb_getlistboxinfo++;
+
+ return CallWindowProcA(oldproc, hwnd, message, wParam, lParam);
+}
+
+static void test_GetListBoxInfo(void)
+{
+ HWND listbox, parent;
+ WNDPROC oldproc;
+ DWORD ret;
+
+ parent = create_parent();
+ listbox = create_listbox(WS_CHILD | WS_VISIBLE, parent);
+
+ oldproc = (WNDPROC)SetWindowLongPtrA(listbox, GWLP_WNDPROC, (LONG_PTR)listbox_subclass_proc);
+ SetWindowLongPtrA(listbox, GWLP_USERDATA, (LONG_PTR)oldproc);
+
+ lb_getlistboxinfo = 0;
+ ret = GetListBoxInfo(listbox);
+ ok(ret > 0, "got %d\n", ret);
+ ok(lb_getlistboxinfo == 1, "got %d\n", lb_getlistboxinfo);
+
+ DestroyWindow(listbox);
+ DestroyWindow(parent);
+}
+
+static void test_missing_lbuttonup(void)
+{
+ HWND listbox, parent, capture;
+
+ parent = create_parent();
+ listbox = create_listbox(WS_CHILD | WS_VISIBLE, parent);
+
+ /* Send button down without a corresponding button up */
+ SendMessageA(listbox, WM_LBUTTONDOWN, 0, MAKELPARAM(10, 10));
+ capture = GetCapture();
+ ok(capture == listbox, "got %p expected %p\n", capture, listbox);
+
+ /* Capture is released and LBN_SELCHANGE sent during WM_KILLFOCUS */
+ got_selchange = 0;
+ SetFocus(NULL);
+ capture = GetCapture();
+ ok(capture == NULL, "got %p\n", capture);
+ ok(got_selchange, "got %d\n", got_selchange);
+
+ DestroyWindow(listbox);
+ DestroyWindow(parent);
+}
+
+static void test_extents(void)
+{
+ HWND listbox, parent;
+ SCROLLINFO sinfo;
+ DWORD res;
+ BOOL br;
+
+ parent = create_parent();
+
+ listbox = create_listbox(WS_CHILD | WS_VISIBLE, parent);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 0, "Got wrong initial horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 100, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) == 0,
+ "List box should not have a horizontal scroll bar\n");
+
+ /* horizontal extent < width */
+ SendMessageA(listbox, LB_SETHORIZONTALEXTENT, 64, 0);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 64, "Got wrong horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 100, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) == 0,
+ "List box should not have a horizontal scroll bar\n");
+
+ /* horizontal extent > width */
+ SendMessageA(listbox, LB_SETHORIZONTALEXTENT, 184, 0);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 184, "Got wrong horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 100, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) == 0,
+ "List box should not have a horizontal scroll bar\n");
+
+ DestroyWindow(listbox);
+
+ listbox = create_listbox(WS_CHILD | WS_VISIBLE | WS_HSCROLL, parent);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 0, "Got wrong initial horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 100, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) == 0,
+ "List box should not have a horizontal scroll bar\n");
+
+ /* horizontal extent < width */
+ SendMessageA(listbox, LB_SETHORIZONTALEXTENT, 64, 0);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 64, "Got wrong horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 63, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) == 0,
+ "List box should not have a horizontal scroll bar\n");
+
+ /* horizontal extent > width */
+ SendMessageA(listbox, LB_SETHORIZONTALEXTENT, 184, 0);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 184, "Got wrong horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 183, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) != 0,
+ "List box should have a horizontal scroll bar\n");
+
+ SendMessageA(listbox, LB_SETHORIZONTALEXTENT, 0, 0);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 0, "Got wrong horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 0, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) == 0,
+ "List box should not have a horizontal scroll bar\n");
+
+ DestroyWindow(listbox);
+
+
+ listbox = create_listbox(WS_CHILD | WS_VISIBLE | WS_HSCROLL | LBS_DISABLENOSCROLL, parent);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 0, "Got wrong initial horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 0, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) != 0,
+ "List box should have a horizontal scroll bar\n");
+
+ /* horizontal extent < width */
+ SendMessageA(listbox, LB_SETHORIZONTALEXTENT, 64, 0);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 64, "Got wrong horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 63, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) != 0,
+ "List box should have a horizontal scroll bar\n");
+
+ /* horizontal extent > width */
+ SendMessageA(listbox, LB_SETHORIZONTALEXTENT, 184, 0);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 184, "Got wrong horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 183, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) != 0,
+ "List box should have a horizontal scroll bar\n");
+
+ SendMessageA(listbox, LB_SETHORIZONTALEXTENT, 0, 0);
+
+ res = SendMessageA(listbox, LB_GETHORIZONTALEXTENT, 0, 0);
+ ok(res == 0, "Got wrong horizontal extent: %u\n", res);
+
+ sinfo.cbSize = sizeof(sinfo);
+ sinfo.fMask = SIF_RANGE;
+ br = GetScrollInfo(listbox, SB_HORZ, &sinfo);
+ ok(br == TRUE, "GetScrollInfo failed\n");
+ ok(sinfo.nMin == 0, "got wrong min: %u\n", sinfo.nMin);
+ ok(sinfo.nMax == 0, "got wrong max: %u\n", sinfo.nMax);
+ ok((GetWindowLongA(listbox, GWL_STYLE) & WS_HSCROLL) != 0,
+ "List box should have a horizontal scroll bar\n");
+
+ DestroyWindow(listbox);
+
+ DestroyWindow(parent);
+}
+
+static void test_listbox(void)
+{
+ static const struct listbox_test SS =
+ /* {add_style} */
+ {{0},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0},
+ { 1, 1, 1, LB_ERR}, {0,0,0,0},
+ { 2, 2, 2, LB_ERR}, {0,0,0,0},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0}};
+
+ /* {selected, anchor, caret, selcount}{TODO fields} */
+ static const struct listbox_test SS_NS =
+ {{LBS_NOSEL},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0},
+ { 1, 1, 1, LB_ERR}, {0,0,0,0},
+ { 2, 2, 2, LB_ERR}, {0,0,0,0},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0}};
+
+ static const struct listbox_test MS =
+ {{LBS_MULTIPLESEL},
+ { 0, LB_ERR, 0, 0}, {0,0,0,0},
+ { 1, 1, 1, 1}, {0,0,0,0},
+ { 2, 1, 2, 1}, {0,0,0,0},
+ { 0, LB_ERR, 0, 2}, {0,0,0,0}};
+
+ static const struct listbox_test MS_NS =
+ {{LBS_MULTIPLESEL | LBS_NOSEL},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0},
+ { 1, 1, 1, LB_ERR}, {0,0,0,0},
+ { 2, 2, 2, LB_ERR}, {0,0,0,0},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0}};
+
+ static const struct listbox_test ES =
+ {{LBS_EXTENDEDSEL},
+ { 0, LB_ERR, 0, 0}, {0,0,0,0},
+ { 1, 1, 1, 1}, {0,0,0,0},
+ { 2, 2, 2, 1}, {0,0,0,0},
+ { 0, LB_ERR, 0, 2}, {0,0,0,0}};
+
+ static const struct listbox_test ES_NS =
+ {{LBS_EXTENDEDSEL | LBS_NOSEL},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0},
+ { 1, 1, 1, LB_ERR}, {0,0,0,0},
+ { 2, 2, 2, LB_ERR}, {0,0,0,0},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0}};
+
+ static const struct listbox_test EMS =
+ {{LBS_EXTENDEDSEL | LBS_MULTIPLESEL},
+ { 0, LB_ERR, 0, 0}, {0,0,0,0},
+ { 1, 1, 1, 1}, {0,0,0,0},
+ { 2, 2, 2, 1}, {0,0,0,0},
+ { 0, LB_ERR, 0, 2}, {0,0,0,0}};
+
+ static const struct listbox_test EMS_NS =
+ {{LBS_EXTENDEDSEL | LBS_MULTIPLESEL | LBS_NOSEL},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0},
+ { 1, 1, 1, LB_ERR}, {0,0,0,0},
+ { 2, 2, 2, LB_ERR}, {0,0,0,0},
+ {LB_ERR, LB_ERR, 0, LB_ERR}, {0,0,0,0}};
+
+ run_test(SS);
+ run_test(SS_NS);
+ run_test(MS);
+ run_test(MS_NS);
+ run_test(ES);
+ run_test(ES_NS);
+ run_test(EMS);
+ run_test(EMS_NS);
+}
+
+START_TEST(listbox)
+{
+ ULONG_PTR ctx_cookie;
+ HANDLE hCtx;
+
+ if (!load_v6_module(&ctx_cookie, &hCtx))
+ return;
+
+ test_listbox();
+ test_item_height();
+ test_ownerdraw();
+ test_LB_SELITEMRANGE();
+ test_LB_SETCURSEL();
+ test_listbox_height();
+ test_itemfrompoint();
+ test_listbox_item_data();
+ test_listbox_LB_DIR();
+ test_listbox_dlgdir();
+ test_set_count();
+ test_GetListBoxInfo();
+ test_missing_lbuttonup();
+ test_extents();
+
+ unload_v6_module(ctx_cookie, hCtx);
+}
--
2.15.1
1
0
Signed-off-by: Alex Henrie <alexhenrie24(a)gmail.com>
---
dlls/gdi32/tests/font.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/dlls/gdi32/tests/font.c b/dlls/gdi32/tests/font.c
index bd04a8b6b1..b732cde854 100644
--- a/dlls/gdi32/tests/font.c
+++ b/dlls/gdi32/tests/font.c
@@ -1790,17 +1790,18 @@ static void test_GetKerningPairs(void)
kd[i].otmMacDescent, otm.otmMacDescent);
ok(near_match(kd[i].otmMacAscent, otm.otmMacAscent), "expected %d, got %d\n",
kd[i].otmMacAscent, otm.otmMacAscent);
-todo_wine {
+todo_wine
ok(kd[i].otmsCapEmHeight == otm.otmsCapEmHeight, "expected %u, got %u\n",
kd[i].otmsCapEmHeight, otm.otmsCapEmHeight);
+todo_wine
ok(kd[i].otmsXHeight == otm.otmsXHeight, "expected %u, got %u\n",
kd[i].otmsXHeight, otm.otmsXHeight);
- /* FIXME: this one sometimes succeeds due to expected 0, enable it when removing todo */
- if (0) ok(kd[i].otmMacLineGap == otm.otmMacLineGap, "expected %u, got %u\n",
+todo_wine_if(kd[i].otmMacLineGap)
+ ok(kd[i].otmMacLineGap == otm.otmMacLineGap, "expected %u, got %u\n",
kd[i].otmMacLineGap, otm.otmMacLineGap);
+todo_wine
ok(kd[i].otmusMinimumPPEM == otm.otmusMinimumPPEM, "expected %u, got %u\n",
kd[i].otmusMinimumPPEM, otm.otmusMinimumPPEM);
-}
total_kern_pairs = GetKerningPairsW(hdc, 0, NULL);
trace("total_kern_pairs %u\n", total_kern_pairs);
--
2.15.1
2
3
19 Dec '17
Signed-off-by: Alex Henrie <alexhenrie24(a)gmail.com>
---
Fixes test crash (floating point exception due to double value outside
of integer range) with TAMu_Kadampari.ttf from Arch Linux's
ttf-indic-otf package. sTypoLineGap is -194 in this font, and Windows 10
sets otmLineGap to 4294967295.
Also fixes the crash with GohaTibebZemen.otf from Arch's xorg-fonts-misc
package. sCapHeight and sxHeight are -32768 in this font, and Windows 10
refuses to install it. However, Windows XP installs the font without
complaint and sets otmsCapEmHeight and otmsXHeight to 4294966903. I
think the 0.00000009% difference from UINT_MAX is just rounding error.
---
dlls/gdi32/freetype.c | 11 ++++++-----
dlls/gdi32/gdi_private.h | 7 +++++++
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/dlls/gdi32/freetype.c b/dlls/gdi32/freetype.c
index 7c5a7ef979..961f9e5f03 100644
--- a/dlls/gdi32/freetype.c
+++ b/dlls/gdi32/freetype.c
@@ -7578,19 +7578,20 @@ static void scale_outline_font_metrics(const GdiFont *font, OUTLINETEXTMETRICW *
#define SCALE_X(x) (x) = GDI_ROUND((double)(x) * (scale_x))
#define SCALE_Y(y) (y) = GDI_ROUND((double)(y) * (scale_y))
+#define SCALE_YU(y) (y) = GDI_ROUND_UINT((double)(y) * (scale_y))
SCALE_Y(potm->otmAscent);
SCALE_Y(potm->otmDescent);
- SCALE_Y(potm->otmLineGap);
- SCALE_Y(potm->otmsCapEmHeight);
- SCALE_Y(potm->otmsXHeight);
+ SCALE_YU(potm->otmLineGap);
+ SCALE_YU(potm->otmsCapEmHeight);
+ SCALE_YU(potm->otmsXHeight);
SCALE_Y(potm->otmrcFontBox.top);
SCALE_Y(potm->otmrcFontBox.bottom);
SCALE_X(potm->otmrcFontBox.left);
SCALE_X(potm->otmrcFontBox.right);
SCALE_Y(potm->otmMacAscent);
SCALE_Y(potm->otmMacDescent);
- SCALE_Y(potm->otmMacLineGap);
+ SCALE_YU(potm->otmMacLineGap);
SCALE_X(potm->otmptSubscriptSize.x);
SCALE_Y(potm->otmptSubscriptSize.y);
SCALE_X(potm->otmptSubscriptOffset.x);
@@ -7599,7 +7600,7 @@ static void scale_outline_font_metrics(const GdiFont *font, OUTLINETEXTMETRICW *
SCALE_Y(potm->otmptSuperscriptSize.y);
SCALE_X(potm->otmptSuperscriptOffset.x);
SCALE_Y(potm->otmptSuperscriptOffset.y);
- SCALE_Y(potm->otmsStrikeoutSize);
+ SCALE_YU(potm->otmsStrikeoutSize);
SCALE_Y(potm->otmsStrikeoutPosition);
SCALE_Y(potm->otmsUnderscoreSize);
SCALE_Y(potm->otmsUnderscorePosition);
diff --git a/dlls/gdi32/gdi_private.h b/dlls/gdi32/gdi_private.h
index 4236aa3690..c591057e3a 100644
--- a/dlls/gdi32/gdi_private.h
+++ b/dlls/gdi32/gdi_private.h
@@ -141,6 +141,13 @@ static inline INT GDI_ROUND(double val)
return (int)floor(val + 0.5);
}
+static inline UINT GDI_ROUND_UINT(double val)
+{
+ double ret = floor(val + 0.5);
+ if (ret > UINT_MAX) return UINT_MAX;
+ return (UINT)ret;
+}
+
#define GET_DC_PHYSDEV(dc,func) \
get_physdev_entry_point( (dc)->physDev, FIELD_OFFSET(struct gdi_dc_funcs,func))
--
2.15.1
3
4
18 Dec '17
Each row of the new Records table allows storing part of the details
of an event or of the state of the TestBot. These pieces of information
are put together into groups and timestamped through the RecordGroups
table. Together these tables allow rebuilding the activity of the
TestBot.
The first user of these new tables is the job scheduler which uses them
to store the new VM status if it changed. This will allow rebuilding
and visualizing the activity of the TestBot for monitoring or debugging.
Signed-off-by: Francois Gouget <fgouget(a)codeweavers.com>
---
This patch requires updating the database with the update29.sql script
and then restarting the TestBot Engine and web server.
testbot/bin/Janitor.pl | 21 ++
testbot/ddl/update29.sql | 20 ++
testbot/ddl/winetestbot.sql | 19 ++
testbot/doc/winetestbot-schema.dia | 338 ++++++++++++++++++++++++++++++++
testbot/lib/WineTestBot/Jobs.pm | 64 +++++-
testbot/lib/WineTestBot/RecordGroups.pm | 117 +++++++++++
testbot/lib/WineTestBot/Records.pm | 126 ++++++++++++
testbot/lib/WineTestBot/VMs.pm | 60 ++++++
8 files changed, 760 insertions(+), 5 deletions(-)
create mode 100644 testbot/ddl/update29.sql
create mode 100644 testbot/lib/WineTestBot/RecordGroups.pm
create mode 100644 testbot/lib/WineTestBot/Records.pm
diff --git a/testbot/bin/Janitor.pl b/testbot/bin/Janitor.pl
index c07c21bd..4f0e4de0 100755
--- a/testbot/bin/Janitor.pl
+++ b/testbot/bin/Janitor.pl
@@ -5,6 +5,7 @@
# archives old jobs and purges older jobs and patches.
#
# Copyright 2009 Ge van Geldorp
+# Copyright 2017 Francois Gouget
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -45,6 +46,7 @@ use WineTestBot::Log;
use WineTestBot::Patches;
use WineTestBot::PendingPatchSets;
use WineTestBot::CGI::Sessions;
+use WineTestBot::RecordGroups;
use WineTestBot::Users;
use WineTestBot::VMs;
@@ -267,3 +269,22 @@ else
{
LogMsg "0Unable to open '$DataDir/staging': $!";
}
+
+# Delete obsolete record groups
+if ($JobPurgeDays != 0)
+{
+ $DeleteBefore = time() - $JobPurgeDays * 86400;
+ my $RecordGroups = CreateRecordGroups();
+ foreach my $RecordGroup (@{$RecordGroups->GetItems()})
+ {
+ if ($RecordGroup->Timestamp < $DeleteBefore)
+ {
+ my $ErrMessage = $RecordGroups->DeleteItem($RecordGroup);
+ if (defined($ErrMessage))
+ {
+ LogMsg $ErrMessage, "\n";
+ }
+ }
+ }
+ $RecordGroups = undef;
+}
diff --git a/testbot/ddl/update29.sql b/testbot/ddl/update29.sql
new file mode 100644
index 00000000..af49814f
--- /dev/null
+++ b/testbot/ddl/update29.sql
@@ -0,0 +1,20 @@
+USE winetestbot;
+
+CREATE TABLE RecordGroups
+(
+ Id INT(6) NOT NULL AUTO_INCREMENT,
+ Timestamp DATETIME NOT NULL,
+ PRIMARY KEY (Id)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE Records
+(
+ RecordGroupId INT(6) NOT NULL,
+ Type ENUM('engine', 'tasks', 'vmresult', 'vmstatus') NOT NULL,
+ Name VARCHAR(96) NOT NULL,
+ Value VARCHAR(64) NULL,
+ PRIMARY KEY (RecordGroupId, Type, Name),
+ FOREIGN KEY (RecordGroupId) REFERENCES RecordGroups(Id)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/testbot/ddl/winetestbot.sql b/testbot/ddl/winetestbot.sql
index 12c1b689..ad8d1a92 100644
--- a/testbot/ddl/winetestbot.sql
+++ b/testbot/ddl/winetestbot.sql
@@ -158,6 +158,25 @@ CREATE TABLE Tasks
)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
+CREATE TABLE RecordGroups
+(
+ Id INT(6) NOT NULL AUTO_INCREMENT,
+ Timestamp DATETIME NOT NULL,
+ PRIMARY KEY (Id)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+CREATE TABLE Records
+(
+ RecordGroupId INT(6) NOT NULL,
+ Type ENUM('engine', 'tasks', 'vmresult', 'vmstatus') NOT NULL,
+ Name VARCHAR(96) NOT NULL,
+ Value VARCHAR(64) NULL,
+ PRIMARY KEY (RecordGroupId, Type, Name),
+ FOREIGN KEY (RecordGroupId) REFERENCES RecordGroups(Id)
+)
+ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
INSERT INTO Roles (Name, IsDefaultRole) VALUES('admin', 'N');
INSERT INTO Roles (Name, IsDefaultRole) VALUES('wine-devel', 'Y');
diff --git a/testbot/doc/winetestbot-schema.dia b/testbot/doc/winetestbot-schema.dia
index 9092c417..ef7c3ca7 100644
--- a/testbot/doc/winetestbot-schema.dia
+++ b/testbot/doc/winetestbot-schema.dia
@@ -3218,5 +3218,343 @@
<dia:real val="0.10000000000000001"/>
</dia:attribute>
</dia:object>
+ <dia:object type="Database - Table" version="0" id="O23">
+ <dia:attribute name="obj_pos">
+ <dia:point val="14.12,3.7"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="14.12,3.7;24.66,8.3"/>
+ </dia:attribute>
+ <dia:attribute name="meta">
+ <dia:composite type="dict"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="14.12,3.7"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="10.539999999999999"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="4.6000000000000005"/>
+ </dia:attribute>
+ <dia:attribute name="name">
+ <dia:string>#Records#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="visible_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="underline_primary_key">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="tagging_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="bold_primary_keys">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="attributes">
+ <dia:composite type="table_attribute">
+ <dia:attribute name="name">
+ <dia:string>#RecordGroupId#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT(6)#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="default_value">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ </dia:composite>
+ <dia:composite type="table_attribute">
+ <dia:attribute name="name">
+ <dia:string>#Type#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#ENUM#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="default_value">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ </dia:composite>
+ <dia:composite type="table_attribute">
+ <dia:attribute name="name">
+ <dia:string>#Name#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#VARCHAR(96)#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="default_value">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ </dia:composite>
+ <dia:composite type="table_attribute">
+ <dia:attribute name="name">
+ <dia:string>#Value#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#VARCHAR(64)#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="default_value">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="normal_font">
+ <dia:font family="monospace" style="0" name="Courier"/>
+ </dia:attribute>
+ <dia:attribute name="name_font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font">
+ <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font_height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="name_font_height">
+ <dia:real val="0.99999999999999989"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font_height">
+ <dia:real val="0.69999999999999996"/>
+ </dia:attribute>
+ <dia:attribute name="text_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="fill_colour">
+ <dia:color val="#ffffffff"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Database - Table" version="0" id="O24">
+ <dia:attribute name="obj_pos">
+ <dia:point val="2.61,3.7125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="2.61,3.7125;10.455,6.7125"/>
+ </dia:attribute>
+ <dia:attribute name="meta">
+ <dia:composite type="dict"/>
+ </dia:attribute>
+ <dia:attribute name="elem_corner">
+ <dia:point val="2.61,3.7125"/>
+ </dia:attribute>
+ <dia:attribute name="elem_width">
+ <dia:real val="7.8449999999999998"/>
+ </dia:attribute>
+ <dia:attribute name="elem_height">
+ <dia:real val="3"/>
+ </dia:attribute>
+ <dia:attribute name="name">
+ <dia:string>#RecordGroups#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="visible_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="underline_primary_key">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="tagging_comment">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="bold_primary_keys">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="attributes">
+ <dia:composite type="table_attribute">
+ <dia:attribute name="name">
+ <dia:string>#Id#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#INT(6)#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="true"/>
+ </dia:attribute>
+ <dia:attribute name="default_value">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ </dia:composite>
+ <dia:composite type="table_attribute">
+ <dia:attribute name="name">
+ <dia:string>#Timestamp#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="type">
+ <dia:string>#DATETIME#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="comment">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ <dia:attribute name="primary_key">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="nullable">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="unique">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="default_value">
+ <dia:string>##</dia:string>
+ </dia:attribute>
+ </dia:composite>
+ </dia:attribute>
+ <dia:attribute name="normal_font">
+ <dia:font family="monospace" style="0" name="Courier"/>
+ </dia:attribute>
+ <dia:attribute name="name_font">
+ <dia:font family="sans" style="80" name="Helvetica-Bold"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font">
+ <dia:font family="sans" style="8" name="Helvetica-Oblique"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font_height">
+ <dia:real val="0.80000000000000004"/>
+ </dia:attribute>
+ <dia:attribute name="name_font_height">
+ <dia:real val="0.99999999999999989"/>
+ </dia:attribute>
+ <dia:attribute name="comment_font_height">
+ <dia:real val="0.69999999999999996"/>
+ </dia:attribute>
+ <dia:attribute name="text_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="fill_colour">
+ <dia:color val="#ffffffff"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ </dia:object>
+ <dia:object type="Database - Reference" version="0" id="O25">
+ <dia:attribute name="obj_pos">
+ <dia:point val="10.455,5.4125"/>
+ </dia:attribute>
+ <dia:attribute name="obj_bb">
+ <dia:rectangle val="10.405,4.75;14.17,5.4625"/>
+ </dia:attribute>
+ <dia:attribute name="meta">
+ <dia:composite type="dict"/>
+ </dia:attribute>
+ <dia:attribute name="orth_points">
+ <dia:point val="10.455,5.4125"/>
+ <dia:point val="11.95,5.4125"/>
+ <dia:point val="11.95,5.4"/>
+ <dia:point val="14.12,5.4"/>
+ </dia:attribute>
+ <dia:attribute name="orth_orient">
+ <dia:enum val="0"/>
+ <dia:enum val="1"/>
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="orth_autoroute">
+ <dia:boolean val="false"/>
+ </dia:attribute>
+ <dia:attribute name="text_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_colour">
+ <dia:color val="#000000ff"/>
+ </dia:attribute>
+ <dia:attribute name="line_width">
+ <dia:real val="0.10000000000000001"/>
+ </dia:attribute>
+ <dia:attribute name="line_style">
+ <dia:enum val="0"/>
+ <dia:real val="1"/>
+ </dia:attribute>
+ <dia:attribute name="corner_radius">
+ <dia:real val="0"/>
+ </dia:attribute>
+ <dia:attribute name="end_arrow">
+ <dia:enum val="0"/>
+ </dia:attribute>
+ <dia:attribute name="start_point_desc">
+ <dia:string>#1#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="end_point_desc">
+ <dia:string>#1..n#</dia:string>
+ </dia:attribute>
+ <dia:attribute name="normal_font">
+ <dia:font family="monospace" style="0" name="Courier"/>
+ </dia:attribute>
+ <dia:attribute name="normal_font_height">
+ <dia:real val="0.59999999999999998"/>
+ </dia:attribute>
+ <dia:connections>
+ <dia:connection handle="0" to="O24" connection="13"/>
+ <dia:connection handle="1" to="O23" connection="12"/>
+ </dia:connections>
+ </dia:object>
</dia:layer>
</dia:diagram>
diff --git a/testbot/lib/WineTestBot/Jobs.pm b/testbot/lib/WineTestBot/Jobs.pm
index 0144ddec..73fc34eb 100644
--- a/testbot/lib/WineTestBot/Jobs.pm
+++ b/testbot/lib/WineTestBot/Jobs.pm
@@ -377,6 +377,7 @@ use WineTestBot::WineTestBotObjects;
use WineTestBot::Branches;
use WineTestBot::Config;
use WineTestBot::Patches;
+use WineTestBot::RecordGroups;
use WineTestBot::Steps;
use WineTestBot::Users;
use WineTestBot::VMs;
@@ -488,10 +489,9 @@ kept on standby so they are ready when their turn comes.
=back
=cut
-sub ScheduleOnHost($$$)
+sub ScheduleOnHost($$$$)
{
- my ($ScopeObject, $SortedJobs, $Hypervisors) = @_;
-
+ my ($ScopeObject, $SortedJobs, $Hypervisors, $Records) = @_;
my $HostVMs = CreateVMs($ScopeObject);
$HostVMs->FilterEnabledRole();
@@ -580,7 +580,7 @@ sub ScheduleOnHost($$$)
{
my $ErrMessage = $Task->Run($Step);
return $ErrMessage if (defined $ErrMessage);
-
+ $VM->RecordStatus($Records, join(" ", "running", $Job->Id, $Step->No, $Task->No));
$Job->UpdateStatus();
$IdleCount--;
$RunningCount++;
@@ -655,6 +655,7 @@ sub ScheduleOnHost($$$)
my $ErrMessage = $VM->RunPowerOff();
return $ErrMessage if (defined $ErrMessage);
+ $VM->RecordStatus($Records, "dirty poweroff");
}
# Power off some idle VMs we don't need immediately so we can revert more
@@ -672,6 +673,7 @@ sub ScheduleOnHost($$$)
my $ErrMessage = $VM->RunPowerOff();
return $ErrMessage if (defined $ErrMessage);
+ $VM->RecordStatus($Records, "dirty poweroff");
$PlannedActiveCount--;
last if ($PlannedActiveCount <= $MaxActiveVMs);
}
@@ -733,6 +735,8 @@ sub ScheduleOnHost($$$)
return undef;
}
+my $_LastTaskCounts = "";
+
=pod
=over 12
@@ -746,12 +750,40 @@ them using WineTestBot::Jobs::ScheduleOnHost().
sub ScheduleJobs()
{
+ my $RecordGroups = CreateRecordGroups();
+ my $RecordGroup = $RecordGroups->Add();
+ my $Records = $RecordGroup->Records;
+ # Save the new RecordGroup now so its Id is lower than those of the groups
+ # created by the scripts called from the scheduler.
+ $RecordGroups->Save();
+
my $Jobs = CreateJobs();
$Jobs->AddFilter("Status", ["queued", "running"]);
my @SortedJobs = sort CompareJobPriority @{$Jobs->GetItems()};
# Note that even if there are no jobs to schedule
# we should check if there are VMs to revert
+ # Count the runnable and queued tasks for the record
+ my ($RunnableTasks, $QueuedTasks) = (0, 0);
+ foreach my $Job (@SortedJobs)
+ {
+ my $Steps = $Job->Steps;
+ $Steps->AddFilter("Status", ["queued", "running"]);
+ my @SortedSteps = sort { $a->No <=> $b->No } @{$Steps->GetItems()};
+ next if (!@SortedSteps);
+
+ my $Tasks = $SortedSteps[0]->Tasks;
+ $Tasks->AddFilter("Status", ["queued"]);
+ $RunnableTasks += @{$Tasks->GetItems()};
+
+ foreach my $Step (@SortedSteps)
+ {
+ my $Tasks = $Step->Tasks;
+ $Tasks->AddFilter("Status", ["queued"]);
+ $QueuedTasks += scalar(@{$Tasks->GetItems()});
+ }
+ }
+
my %Hosts;
my $VMs = CreateVMs($Jobs);
$VMs->FilterEnabledRole();
@@ -765,9 +797,31 @@ sub ScheduleJobs()
foreach my $Host (keys %Hosts)
{
my @HostHypervisors = keys %{$Hosts{$Host}};
- my $HostErrMessage = ScheduleOnHost($Jobs, \@SortedJobs, \@HostHypervisors);
+ my $HostErrMessage = ScheduleOnHost($Jobs, \@SortedJobs, \@HostHypervisors, $Records);
push @ErrMessages, $HostErrMessage if (defined $HostErrMessage);
}
+
+ # Note that any VM Status or Role change will trigger ScheduleJobs() so this
+ # records all VM state changes.
+ $VMs = CreateVMs();
+ map { $_->RecordStatus($Records) } (@{$VMs->GetItems()});
+ if (@{$Records->GetItems()})
+ {
+ # FIXME Add the number of tasks scheduled to run on a maintenance, retired
+ # or deleted VM...
+ my $TaskCounts = "$RunnableTasks $QueuedTasks 0";
+ if ($TaskCounts ne $_LastTaskCounts)
+ {
+ $Records->AddRecord('tasks', 'counters', $TaskCounts);
+ $_LastTaskCounts = $TaskCounts;
+ }
+ $RecordGroups->Save();
+ }
+ else
+ {
+ $RecordGroups->DeleteItem($RecordGroup);
+ }
+
return @ErrMessages ? join("\n", @ErrMessages) : undef;
}
diff --git a/testbot/lib/WineTestBot/RecordGroups.pm b/testbot/lib/WineTestBot/RecordGroups.pm
new file mode 100644
index 00000000..b33d16eb
--- /dev/null
+++ b/testbot/lib/WineTestBot/RecordGroups.pm
@@ -0,0 +1,117 @@
+# -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*-
+# Copyright 2017 Francois Gouget
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+use strict;
+
+package WineTestBot::RecordGroup;
+
+=head1 NAME
+
+WineTestBot::RecordGroup - a group of related history records
+
+=head1 DESCRIPTION
+
+A RecordGroup is a group of WineTestBot::Record objects describing an event
+or the state of the TestBot at at a given time.
+
+=cut
+
+use WineTestBot::WineTestBotObjects;
+use WineTestBot::Config;
+
+use vars qw (@ISA @EXPORT);
+
+require Exporter;
+(a)ISA = qw(WineTestBot::WineTestBotItem Exporter);
+
+sub InitializeNew($$)
+{
+ my ($self, $Collection) = @_;
+
+ $self->Timestamp(time());
+
+ $self->SUPER::InitializeNew($Collection);
+}
+
+
+package WineTestBot::RecordGroups;
+
+=head1 NAME
+
+WineTestBot::RecordGroups - A collection of WineTestBot::RecordGroup objects
+
+=cut
+
+use ObjectModel::BasicPropertyDescriptor;
+use ObjectModel::EnumPropertyDescriptor;
+use ObjectModel::DetailrefPropertyDescriptor;
+use ObjectModel::PropertyDescriptor;
+use WineTestBot::WineTestBotObjects;
+use WineTestBot::Records;
+
+use vars qw (@ISA @EXPORT @PropertyDescriptors);
+
+require Exporter;
+(a)ISA = qw(WineTestBot::WineTestBotCollection Exporter);
+(a)EXPORT = qw(&CreateRecordGroups &SaveRecord);
+
+
+BEGIN
+{
+ @PropertyDescriptors = (
+ CreateBasicPropertyDescriptor("Id", "Group id", 1, 1, "S", 6),
+ CreateBasicPropertyDescriptor("Timestamp", "Timestamp", !1, 1, "DT", 19),
+ CreateDetailrefPropertyDescriptor("Records", "Records", !1, !1, \&CreateRecords),
+ );
+}
+
+sub CreateItem($)
+{
+ my ($self) = @_;
+
+ return WineTestBot::RecordGroup->new($self);
+}
+
+sub CreateRecordGroups(;$)
+{
+ my ($ScopeObject) = @_;
+ return WineTestBot::RecordGroups->new("RecordGroups", "RecordGroups", "RecordGroup",
+ \@PropertyDescriptors, $ScopeObject);
+}
+
+=pod
+=over 12
+
+=item C<SaveRecord()>
+
+Creates and saves a standalone record.
+
+=back
+=cut
+
+sub SaveRecord($$;$)
+{
+ my ($Type, $Name, $Value) = @_;
+
+ my $RecordGroups = CreateRecordGroups();
+ my $Records = $RecordGroups->Add()->Records;
+ $Records->AddRecord($Type, $Name, $Value);
+
+ return $RecordGroups->Save();
+}
+
+1;
diff --git a/testbot/lib/WineTestBot/Records.pm b/testbot/lib/WineTestBot/Records.pm
new file mode 100644
index 00000000..dd0fec97
--- /dev/null
+++ b/testbot/lib/WineTestBot/Records.pm
@@ -0,0 +1,126 @@
+# -*- Mode: Perl; perl-indent-level: 2; indent-tabs-mode: nil -*-
+# Copyright 2017 Francois Gouget
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+
+use strict;
+
+package WineTestBot::Record;
+
+=head1 NAME
+
+WineTestBot::Record - records part of an event or the state of the TestBot.
+
+=head1 DESCRIPTION
+
+A Record is created to save information about an event or part of the state of
+the TestBot at a given time. A full description of said event or state may
+require a variable number of Records so they are part of a RecordGroup which
+identifies which other Records relate to the same event or state.
+The RecordGroup also stores the timestamp of the event or state.
+
+The point of the Record objects is to keep a record of the activity of the
+TestBot. By putting them together it is possible to rebuild what the TestBot
+did and when, for debugging or performance analysis. The amount of details is
+only limited by the amount of data dumped into the Records table.
+
+=cut
+
+use WineTestBot::Config;
+use WineTestBot::WineTestBotObjects;
+
+use vars qw (@ISA @EXPORT);
+
+require Exporter;
+(a)ISA = qw(WineTestBot::WineTestBotItem Exporter);
+
+sub InitializeNew($$)
+{
+ my ($self, $Collection) = @_;
+
+ $self->SUPER::InitializeNew($Collection);
+}
+
+
+package WineTestBot::Records;
+
+=head1 NAME
+
+WineTestBot::Records - A collection of WineTestBot::Record objects
+
+=cut
+
+use ObjectModel::BasicPropertyDescriptor;
+use ObjectModel::EnumPropertyDescriptor;
+use ObjectModel::PropertyDescriptor;
+use WineTestBot::WineTestBotObjects;
+
+use vars qw (@ISA @EXPORT @PropertyDescriptors);
+
+require Exporter;
+(a)ISA = qw(WineTestBot::WineTestBotCollection Exporter);
+(a)EXPORT = qw(&CreateRecords);
+
+
+BEGIN
+{
+ @PropertyDescriptors = (
+ CreateEnumPropertyDescriptor("Type", "Type", 1, 1, ['engine', 'tasks', 'vmresult', 'vmstatus']),
+ CreateBasicPropertyDescriptor("Name", "Name", 1, 1, "A", 96),
+ CreateBasicPropertyDescriptor("Value", "Value", !1, !1, "A", 64),
+ );
+}
+
+sub CreateItem($)
+{
+ my ($self) = @_;
+
+ return WineTestBot::Record->new($self);
+}
+
+sub CreateRecords(;$$)
+{
+ my ($ScopeObject, $RecordGroup) = @_;
+ return WineTestBot::Records->new("Records", "Records", "Record",
+ \@PropertyDescriptors, $ScopeObject,
+ $RecordGroup);
+}
+
+=pod
+=over 12
+
+=item C<AddRecord()>
+
+This is a convenience function for adding a new record to a Records collection
+and setting its properties at the same time.
+
+=back
+=cut
+
+sub AddRecord($$$;$)
+{
+ my ($self, $Type, $Name, $Value) = @_;
+
+ my $Record = $self->Add();
+ my $TemporaryKey = $Record->GetKey();
+ $Record->Type($Type);
+ $Record->Name($Name);
+ $Record->Value($Value) if (defined $Value);
+ $self->KeyChanged($TemporaryKey, $Record->GetKey());
+
+ return $Record;
+}
+
+1;
diff --git a/testbot/lib/WineTestBot/VMs.pm b/testbot/lib/WineTestBot/VMs.pm
index 7db0aa51..a8da842b 100644
--- a/testbot/lib/WineTestBot/VMs.pm
+++ b/testbot/lib/WineTestBot/VMs.pm
@@ -148,6 +148,7 @@ use ObjectModel::BackEnd;
use WineTestBot::Config;
use WineTestBot::Engine::Notify;
use WineTestBot::LibvirtDomain;
+use WineTestBot::RecordGroups;
use WineTestBot::TestAgent;
use WineTestBot::WineTestBotObjects;
@@ -175,6 +176,13 @@ sub InitializeNew($$)
$self->SUPER::InitializeNew($Collection);
}
+sub HasEnabledRole($)
+{
+ my ($self) = @_;
+ # Filter out the disabled VMs, that is the retired and deleted ones
+ return $self->Role ne "retired" && $self->Role ne "deleted";
+}
+
sub GetHost($)
{
my ($self) = @_;
@@ -499,6 +507,58 @@ sub RunRevert($)
return $self->_RunVMTool("reverting", ["--log-only", "revert", $self->GetKey()]);
}
+=pod
+=over 12
+
+=item C<GetRecordName()>
+
+Provides the name to use for history records related to this VM.
+
+=back
+=cut
+
+sub GetRecordName($)
+{
+ my ($self) = @_;
+ return $self->Name ." ". $self->GetHost();
+}
+
+my %_VMStatuses;
+
+=pod
+=over 12
+
+=item C<RecordStatus()>
+
+Adds a Record if the status of the VM changed since the last recorded status.
+
+=back
+=cut
+
+sub RecordStatus($$;$)
+{
+ my ($self, $Records, $RecordStatus) = @_;
+
+ $RecordStatus ||= $self->HasEnabledRole() ? $self->Status : $self->Role;
+ my $NewStatus = $self->GetHost() ." $RecordStatus";
+
+ my $LastStatus = $_VMStatuses{$self->Name} || "";
+ # Don't add a record if nothing changed
+ return if ($LastStatus eq $NewStatus);
+ # Or if the new status is less complete
+ return if ($LastStatus =~ /^\Q$NewStatus \E/);
+
+ $_VMStatuses{$self->Name} = $NewStatus;
+ if ($Records)
+ {
+ $Records->AddRecord('vmstatus', $self->GetRecordName(), $RecordStatus);
+ }
+ else
+ {
+ SaveRecord('vmstatus', $self->GetRecordName(), $RecordStatus);
+ }
+}
+
package WineTestBot::VMs;
--
2.15.1
1
3
From: Mingcong Bai <jeffbai(a)aosc.xyz>
Signed-off-by: Mingcong Bai <jeffbai(a)aosc.xyz>
Signed-off-by: Jactry Zeng <jzeng(a)codeweavers.com>
---
po/zh_CN.po | 107
++++++++++++++++--------------------------------------------
1 file changed, 29 insertions(+), 78 deletions(-)
3
2
Superseded patch 139632.
From: Mingcong Bai <jeffbai(a)aosc.xyz>
Signed-off-by: Mingcong Bai <jeffbai(a)aosc.xyz>
Signed-off-by: Jactry Zeng <jzeng(a)codeweavers.com>
---
po/zh_CN.po | 99
++++++++++++++++---------------------------------------------
1 file changed, 25 insertions(+), 74 deletions(-)
1
0