/** @file
* Automated test of the X11 seamless Additions code.
* @todo Better separate test data from implementation details!
*/
/*
* Copyright (C) 2007-2011 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#include <stdlib.h> /* exit() */
#include <X11/Xatom.h>
#include <X11/Xmu/WinUtil.h>
#include <iprt/initterm.h>
#include <iprt/mem.h>
#include <iprt/path.h>
#include <iprt/semaphore.h>
#include <iprt/stream.h>
#include <iprt/string.h>
#include "../seamless.h"
#undef DefaultRootWindow
/******************************************************
* Mock X11 functions needed by the seamless X11 class *
******************************************************/
int XFree(void *data)
{
RTMemFree(data);
return 0;
}
#define TEST_DISPLAY ((Display *)0xffff)
#define TEST_ROOT ((Window)1)
extern "C" Display *XOpenDisplay(const char *display_name);
Display *XOpenDisplay(const char *display_name)
{
return TEST_DISPLAY;
}
extern "C" int XCloseDisplay(Display *display);
int XCloseDisplay(Display *display)
{
Assert(display == TEST_DISPLAY);
return 0;
}
enum
{
ATOM_PROP = 1,
ATOM_DESKTOP_PROP
};
extern "C" Atom XInternAtom(Display *display, const char *atom_name,
Bool only_if_exists);
Atom XInternAtom(Display *display, const char *atom_name, Bool only_if_exists)
{
Assert(display == TEST_DISPLAY);
if (!RTStrCmp(atom_name, WM_TYPE_PROP))
return (Atom) ATOM_PROP;
if (!RTStrCmp(atom_name, WM_TYPE_DESKTOP_PROP))
return (Atom) ATOM_DESKTOP_PROP;
AssertFailed();
return (Atom)0;
}
/** The window (if any) on which the WM_TYPE_PROP property is set to the
* WM_TYPE_DESKTOP_PROP atom. */
static Window g_hSmlsDesktopWindow = 0;
extern "C" int XGetWindowProperty(Display *display, Window w, Atom property,
long long_offset, long long_length,
Bool delProp, Atom req_type,
Atom *actual_type_return,
int *actual_format_return,
unsigned long *nitems_return,
unsigned long *bytes_after_return,
unsigned char **prop_return);
int XGetWindowProperty(Display *display, Window w, Atom property,
long long_offset, long long_length, Bool delProp,
Atom req_type, Atom *actual_type_return,
int *actual_format_return,
unsigned long *nitems_return,
unsigned long *bytes_after_return,
unsigned char **prop_return)
{
Assert(display == TEST_DISPLAY);
Atom atomType = XInternAtom (display, WM_TYPE_PROP, true);
Atom atomTypeDesktop = XInternAtom (display, WM_TYPE_DESKTOP_PROP, true);
/* We only handle things we expect. */
AssertReturn((req_type == XA_ATOM) || (req_type == AnyPropertyType),
0xffff);
AssertReturn(property == atomType, 0xffff);
*actual_type_return = XA_ATOM;
*actual_format_return = sizeof(Atom) * 8;
*nitems_return = 0;
*bytes_after_return = sizeof(Atom);
*prop_return = NULL;
if ((w != g_hSmlsDesktopWindow) || (g_hSmlsDesktopWindow == 0))
return Success;
AssertReturn(long_offset == 0, 0);
AssertReturn(delProp == false, 0);
unsigned char *pProp;
pProp = (unsigned char *)RTMemDup(&atomTypeDesktop,
sizeof(atomTypeDesktop));
AssertReturn(pProp, 0xffff);
*nitems_return = 1;
*prop_return = pProp;
*bytes_after_return = 0;
return 0;
}
/** Sets the current set of properties for all mock X11 windows */
static void smlsSetDesktopWindow(Window hWin)
{
g_hSmlsDesktopWindow = hWin;
}
extern "C" Bool XShapeQueryExtension (Display *dpy, int *event_basep,
int *error_basep);
Bool XShapeQueryExtension (Display *dpy, int *event_basep, int *error_basep)
{
Assert(dpy == TEST_DISPLAY);
return true;
}
/* We silently ignore this for now. */
extern "C" int XSelectInput(Display *display, Window w, long event_mask);
int XSelectInput(Display *display, Window w, long event_mask)
{
Assert(display == TEST_DISPLAY);
return 0;
}
/* We silently ignore this for now. */
extern "C" void XShapeSelectInput(Display *display, Window w,
unsigned long event_mask);
void XShapeSelectInput(Display *display, Window w, unsigned long event_mask)
{
Assert(display == TEST_DISPLAY);
}
extern "C" Window XDefaultRootWindow(Display *display);
Window XDefaultRootWindow(Display *display)
{
Assert(display == TEST_DISPLAY);
return TEST_ROOT;
}
static unsigned g_cSmlsWindows = 0;
static Window *g_paSmlsWindows = NULL;
static XWindowAttributes *g_paSmlsWinAttribs = NULL;
static const char **g_papszSmlsWinNames = NULL;
extern "C" Status XQueryTree(Display *display, Window w, Window *root_return,
Window *parent_return, Window **children_return,
unsigned int *nchildren_return);
Status XQueryTree(Display *display, Window w, Window *root_return,
Window *parent_return, Window **children_return,
unsigned int *nchildren_return)
{
Assert(display == TEST_DISPLAY);
AssertReturn(w == TEST_ROOT, False); /* We support nothing else */
AssertPtrReturn(children_return, False);
AssertReturn(g_paSmlsWindows, False);
if (root_return)
*root_return = TEST_ROOT;
if (parent_return)
*parent_return = TEST_ROOT;
*children_return = (Window *)RTMemDup(g_paSmlsWindows,
g_cSmlsWindows * sizeof(Window));
if (nchildren_return)
*nchildren_return = g_cSmlsWindows;
return (g_cSmlsWindows != 0);
}
extern "C" Window XmuClientWindow(Display *dpy, Window win);
Window XmuClientWindow(Display *dpy, Window win)
{
Assert(dpy == TEST_DISPLAY);
return win;
}
extern "C" Status XGetWindowAttributes(Display *display, Window w,
XWindowAttributes *window_attributes_return);
Status XGetWindowAttributes(Display *display, Window w,
XWindowAttributes *window_attributes_return)
{
Assert(display == TEST_DISPLAY);
AssertPtrReturn(window_attributes_return, 1);
for (unsigned i = 0; i < g_cSmlsWindows; ++i)
if (g_paSmlsWindows[i] == w)
{
*window_attributes_return = g_paSmlsWinAttribs[i];
return 1;
}
return 0;
}
extern "C" Status XGetWMNormalHints(Display *display, Window w,
XSizeHints *hints_return,
long *supplied_return);
Status XGetWMNormalHints(Display *display, Window w,
XSizeHints *hints_return, long *supplied_return)
{
Assert(display == TEST_DISPLAY);
return 1;
}
static void smlsSetWindowAttributes(XWindowAttributes *pAttribs,
Window *pWindows, unsigned cAttribs,
const char **paNames)
{
g_paSmlsWinAttribs = pAttribs;
g_paSmlsWindows = pWindows;
g_cSmlsWindows = cAttribs;
g_papszSmlsWinNames = paNames;
}
static Window g_SmlsShapedWindow = 0;
static int g_cSmlsShapeRectangles = 0;
static XRectangle *g_pSmlsShapeRectangles = NULL;
extern "C" XRectangle *XShapeGetRectangles (Display *dpy, Window window,
int kind, int *count,
int *ordering);
XRectangle *XShapeGetRectangles (Display *dpy, Window window, int kind,
int *count, int *ordering)
{
Assert(dpy == TEST_DISPLAY);
if ((window != g_SmlsShapedWindow) || (window == 0))
return NULL; /* Probably not correct, but works for us. */
*count = g_cSmlsShapeRectangles;
*ordering = 0;
return (XRectangle *)RTMemDup(g_pSmlsShapeRectangles,
sizeof(XRectangle)
* g_cSmlsShapeRectangles);
}
static void smlsSetShapeRectangles(Window window, int cRects,
XRectangle *pRects)
{
g_SmlsShapedWindow = window;
g_cSmlsShapeRectangles = cRects;
g_pSmlsShapeRectangles = pRects;
}
static int g_SmlsEventType = 0;
static Window g_SmlsEventWindow = 0;
/* This should not be needed in the bits of the code we test. */
extern "C" int XNextEvent(Display *display, XEvent *event_return);
int XNextEvent(Display *display, XEvent *event_return)
{
Assert(display == TEST_DISPLAY);
event_return->xany.type = g_SmlsEventType;
event_return->xany.window = g_SmlsEventWindow;
event_return->xmap.window = g_SmlsEventWindow;
return True;
}
static void smlsSetNextEvent(int type, Window window)
{
g_SmlsEventType = type;
g_SmlsEventWindow = window;
}
/* This should not be needed in the bits of the code we test. */
extern "C" Status XSendEvent(Display *display, Window w, Bool propagate,
long event_mask, XEvent *event_send);
Status XSendEvent(Display *display, Window w, Bool propagate,
long event_mask, XEvent *event_send)
{
Assert(display == TEST_DISPLAY);
AssertFailedReturn(0);
}
/* This should not be needed in the bits of the code we test. */
extern "C" int XFlush(Display *display);
int XFlush(Display *display)
{
Assert(display == TEST_DISPLAY);
AssertFailedReturn(0);
}
/** Global "received a notification" flag. */
static bool g_fNotified = false;
/** Dummy host call-back. */
static void sendRegionUpdate(RTRECT *pRects, size_t cRects)
{
g_fNotified = true;
}
static bool gotNotification(void)
{
if (!g_fNotified)
return false;
g_fNotified = false;
return true;
}
/*****************************
* The actual tests to be run *
*****************************/
/** The name of the unit test */
static const char *g_pszTestName = NULL;
/*** Test fixture data and data structures ***/
/** A structure describing a test fixture to be run through. Each fixture
* describes the state of the windows visible (and unmapped) on the X server
* before and after a particular event is delivered, and the expected
* on-screen positions of all interesting visible windows at the end of the
* fixture as reported by the code (currently in the order it is likely to
* report them in, @todo sort this). We expect that the set of visible
* windows will be the same whether we start the code before the event and
* handle it or start the code after the event.
*/
struct SMLSFIXTURE
{
/** The number of windows visible before the event */
unsigned cWindowsBefore;
/** An array of Window IDs for the visible and unmapped windows before
* the event */
Window *pahWindowsBefore;
/** The window attributes matching the windows in @a paWindowsBefore */
XWindowAttributes *paAttribsBefore;
/** The window names matching the windows in @a paWindowsBefore */
const char **papszNamesBefore;
/** The shaped window before the event - we allow at most one of these.
* Zero for none. */
Window hShapeWindowBefore;
/** The number of rectangles in the shaped window before the event. */
int cShapeRectsBefore;
/** The rectangles in the shaped window before the event */
XRectangle *paShapeRectsBefore;
/** The number of windows visible after the event */
unsigned cWindowsAfter;
/** An array of Window IDs for the visible and unmapped windows after
* the event */
Window *pahWindowsAfter;
/** The window attributes matching the windows in @a paWindowsAfter */
XWindowAttributes *paAttribsAfter;
/** The window names matching the windows in @a paWindowsAfter */
const char **papszNamesAfter;
/** The shaped window after the event - we allow at most one of these.
* Zero for none. */
Window hShapeWindowAfter;
/** The number of rectangles in the shaped window after the event. */
int cShapeRectsAfter;
/** The rectangles in the shaped window after the event */
XRectangle *paShapeRectsAfter;
/** The event to delivered */
int x11EventType;
/** The window for which the event in @enmEvent is delivered */
Window hEventWindow;
/** The number of windows expected to be reported at the end of the
* fixture */
unsigned cReportedRects;
/** The onscreen positions of those windows. */
RTRECT *paReportedRects;
/** Do we expect notification after the event? */
bool fExpectNotification;
};
/*** Test fixture to test the code against X11 configure (move) events ***/
static Window g_ahWin1[] = { 20 };
static XWindowAttributes g_aAttrib1Before[] =
{ { 100, 200, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable }
};
static XRectangle g_aRectangle1[] =
{
{ 0, 0, 50, 50 },
{ 50, 50, 150, 250 }
};
static XWindowAttributes g_aAttrib1After[] =
{ { 200, 300, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable }
};
static const char *g_apszNames1[] = { "Test Window" };
AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib1Before));
AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib1After));
AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_apszNames1));
static RTRECT g_aRects1[] =
{
{ 200, 300, 250, 350 },
{ 250, 350, 400, 600 }
};
static SMLSFIXTURE g_testMove =
{
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib1Before,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib1After,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
ConfigureNotify,
20,
RT_ELEMENTS(g_aRects1),
g_aRects1,
true
};
/*** Test fixture to test the code against X11 configure (resize) events ***/
static XWindowAttributes g_aAttrib2Before[] =
{ { 100, 200, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable }
};
static XRectangle g_aRectangle2Before[] =
{
{ 0, 0, 50, 50 },
{ 50, 50, 100, 100 }
};
AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib2Before));
static SMLSFIXTURE g_testResize =
{
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib2Before,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle2Before),
g_aRectangle2Before,
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib1After,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
ConfigureNotify,
20,
RT_ELEMENTS(g_aRects1),
g_aRects1,
true
};
/*** Test fixture to test the code against X11 map events ***/
static XWindowAttributes g_aAttrib3Before[] =
{ { 200, 300, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsUnmapped }
};
AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib3Before));
static SMLSFIXTURE g_testMap =
{
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib3Before,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib1After,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
MapNotify,
20,
RT_ELEMENTS(g_aRects1),
g_aRects1,
true
};
/*** Test fixtures to test the code against X11 unmap events ***/
static XWindowAttributes g_aAttrib4After[] =
{ { 100, 200, 300, 400, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsUnmapped }
};
AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib4After));
static SMLSFIXTURE g_testUnmap =
{
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib1Before,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib4After,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
UnmapNotify,
20,
0,
NULL,
true
};
/*** A window we are not monitoring has been unmapped. Nothing should
*** happen, especially nothing bad. ***/
static RTRECT g_aRects2[] =
{
{ 100, 200, 150, 250 },
{ 150, 250, 300, 500 }
};
static SMLSFIXTURE g_testUnmapOther =
{
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib1Before,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib1Before,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
UnmapNotify,
21,
RT_ELEMENTS(g_aRects2),
g_aRects2,
false
};
/*** Test fixture to test the code against X11 shape events ***/
static XRectangle g_aRectangle5Before[] =
{
{ 0, 0, 200, 200 }
};
static SMLSFIXTURE g_testShape =
{
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib1After,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle5Before),
g_aRectangle5Before,
RT_ELEMENTS(g_ahWin1),
g_ahWin1,
g_aAttrib1After,
g_apszNames1,
20,
RT_ELEMENTS(g_aRectangle1),
g_aRectangle1,
VBoxShapeNotify,
20,
RT_ELEMENTS(g_aRects1),
g_aRects1,
true
};
/*** And the test code proper ***/
/** Compare two RTRECT structures */
static bool smlsCompRect(RTRECT *pFirst, RTRECT *pSecond)
{
return ( (pFirst->xLeft == pSecond->xLeft)
&& (pFirst->yTop == pSecond->yTop)
&& (pFirst->xRight == pSecond->xRight)
&& (pFirst->yBottom == pSecond->yBottom));
}
static void smlsPrintDiffRects(RTRECT *pExp, RTRECT *pGot)
{
RTPrintf(" Expected: %d, %d, %d, %d. Got: %d, %d, %d, %d\n",
pExp->xLeft, pExp->yTop, pExp->xRight, pExp->yBottom,
pGot->xLeft, pGot->yTop, pGot->xRight, pGot->yBottom);
}
/** Run through a test fixture */
static unsigned smlsDoFixture(SMLSFIXTURE *pFixture, const char *pszDesc)
{
SeamlessX11 subject;
unsigned cErrs = 0;
subject.init(sendRegionUpdate);
smlsSetWindowAttributes(pFixture->paAttribsBefore,
pFixture->pahWindowsBefore,
pFixture->cWindowsBefore,
pFixture->papszNamesBefore);
smlsSetShapeRectangles(pFixture->hShapeWindowBefore,
pFixture->cShapeRectsBefore,
pFixture->paShapeRectsBefore);
subject.start();
smlsSetWindowAttributes(pFixture->paAttribsAfter,
pFixture->pahWindowsAfter,
pFixture->cWindowsAfter,
pFixture->papszNamesAfter);
smlsSetShapeRectangles(pFixture->hShapeWindowAfter,
pFixture->cShapeRectsAfter,
pFixture->paShapeRectsAfter);
smlsSetNextEvent(pFixture->x11EventType, pFixture->hEventWindow);
if (gotNotification()) /* Initial window tree rebuild */
{
RTPrintf("%s: fixture: %s. Notification was set before the first event!!!\n",
g_pszTestName, pszDesc);
++cErrs;
}
subject.nextConfigurationEvent();
if (!gotNotification())
{
RTPrintf("%s: fixture: %s. No notification was sent for the initial window tree rebuild.\n",
g_pszTestName, pszDesc);
++cErrs;
}
smlsSetNextEvent(0, 0);
subject.nextConfigurationEvent();
if (pFixture->fExpectNotification && !gotNotification())
{
RTPrintf("%s: fixture: %s. No notification was sent after the event.\n",
g_pszTestName, pszDesc);
++cErrs;
}
RTRECT *pRects = subject.getRects();
size_t cRects = subject.getRectCount();
if (cRects != pFixture->cReportedRects)
{
RTPrintf("%s: fixture: %s. Wrong number of rectangles reported after processing event (expected %u, got %u).\n",
g_pszTestName, pszDesc, pFixture->cReportedRects,
cRects);
++cErrs;
}
else
for (unsigned i = 0; i < cRects; ++i)
if (!smlsCompRect(&pRects[i], &pFixture->paReportedRects[i]))
{
RTPrintf("%s: fixture: %s. Rectangle %u wrong after processing event.\n",
g_pszTestName, pszDesc, i);
smlsPrintDiffRects(&pFixture->paReportedRects[i],
&pRects[i]);
++cErrs;
break;
}
subject.stop();
subject.start();
if (cRects != pFixture->cReportedRects)
{
RTPrintf("%s: fixture: %s. Wrong number of rectangles reported without processing event (expected %u, got %u).\n",
g_pszTestName, pszDesc, pFixture->cReportedRects,
cRects);
++cErrs;
}
else
for (unsigned i = 0; i < cRects; ++i)
if (!smlsCompRect(&pRects[i], &pFixture->paReportedRects[i]))
{
RTPrintf("%s: fixture: %s. Rectangle %u wrong without processing event.\n",
g_pszTestName, pszDesc, i);
smlsPrintDiffRects(&pFixture->paReportedRects[i],
&pRects[i]);
++cErrs;
break;
}
return cErrs;
}
int main( int argc, char **argv)
{
RTR3InitExe(argc, &argv, 0);
unsigned cErrs = 0;
g_pszTestName = RTPathFilename(argv[0]);
RTPrintf("%s: TESTING\n", g_pszTestName);
cErrs += smlsDoFixture(&g_testMove,
"ConfigureNotify event (window moved)");
// Currently not working
cErrs += smlsDoFixture(&g_testResize,
"ConfigureNotify event (window resized)");
cErrs += smlsDoFixture(&g_testMap, "MapNotify event");
cErrs += smlsDoFixture(&g_testUnmap, "UnmapNotify event");
cErrs += smlsDoFixture(&g_testUnmapOther,
"UnmapNotify event for unmonitored window");
cErrs += smlsDoFixture(&g_testShape, "ShapeNotify event");
if (cErrs > 0)
RTPrintf("%u errors\n", cErrs);
return cErrs == 0 ? 0 : 1;
}