|
|
|
date: Tue, 12 Aug 2008 04:39:48 -0700 (PDT),
group: microsoft.public.platformsdk.msi
back
Re: How do I execute deferred CAs programmatically (for unit testing customactions)?
[Please do not mail me a copy of your followup]
"dB." spake the secret code
thusly:
>The one thing I can't figure out is how to execute deferred CAs? I
>tried running the whole sequence via MsiDoAction, but it always fails
>in InstallInitialize with 0x80070643: Fatal error during installation.
I have been pursuing unit tests of my C++ CAs as well. What I do is
treat the CA as a function to be called and I fake out the MSI API
with mock API functions. Since the session MSIHANDLE is an opaque
data type anyway, it won't matter what you pass into your CA for this
handle. It does mean that you have to provide a level of indirection
between your CA and the MSI API so that your unit test can intercept
things at the level of indirection. Here's one way of doing it:
class MsiAPI
{
public:
virtual ~MsiAPI() { }
UINT MsiGetProperty(MSIHANDLE session,
LPCTSTR name, LPTSTR value, DWORD *valueSize) = 0;
};
class ProductionAPI : public MsiAPI
{
public:
virtual ~ProductionAPI() { }
UINT MsiGetProperty(MSIHANDLE session,
LPCTSTR name, LPTSTR value, DWORD *valueSize)
{ return ::MsiGetProperty(session, name, value, valueSize); }
};
typedef std::basic_string<TCHAR> tstring;
class FakeAPI : public MsiAPI
{
public:
FakeAPI() : MsiAPI(),
_lastSession(0),
_fakeValueSize(0),
_fakeMsiGetPropertyResult(ERROR_OK),
_fakeValue(),
_lastName()
{ }
virtual ~FakeAPI() { }
UINT MsiGetProperty(MSIHANDLE session,
LPCTSTR name, LPTSTR value, DWORD *valueSize)
{
_lastSession = session;
_lastName = name;
_lastValueSize = valueSize;
if (!value && valueSize)
{
*valueSize = _fakeValueSize;
}
else if (value && valueSize)
{
::lstrcpyn(value, _fakeValue.c_str(), *valueSize);
}
return _fakeMsiGetPropertyResult;
}
void SetFakeMsiGetPropertyResult(UINT value)
{ _fakeMsiGetPropertyResult = value; }
void SetMsiGetPropertyValueSize(DWORD valueSize)
{ _fakeValueSize = valueSize; }
void SetMsiGetPropertyValue(tstring const &value)
{ _fakeValue = value; }
MSIHANDLE MsiGetPropertyLastSession() const
{ return _lastSession; }
tstring const &MsiGetPropertyLastName() const
{ return _lastName; }
private:
MSIHANDLE _lastSession;
UINT _fakeValueSize;
UINT _fakeMsiGetPropertyResult;
tstring _fakeValue;
tstring _lastName;
};
class MyCustomAction
{
public:
MyCustomAction(MsiAPI &api) : _api(api)
{ }
UINT Process(MSIHANDLE session)
{
TCHAR value[1024];
DWORD valueSize = sizeof(value)/sizeof(value[0]);
UINT result = _api.MsiGetProperty(session,
_T("customProperty"), &value[0], &valueSize);
return ERROR_OK;
}
private:
MsiAPI &_api;
};
// this is the production code called in the real installer
UINT MyCustomActionEntry(MSIHANDLE session)
{
return MyCustomAction(ProductionApi()).Process(session);
}
// this is how you exercise your CA in a test
void TestMyCustomAction()
{
FakeApi api;
MSIHANDLE fakeSession = reinterpret_cast<MSIHANDLE>(0x1);
UINT result = MyCustomAction(api).Process(fakeSession);
assert(fakeSession == api.MsiGetPropertyLastSession());
assert(_T("customProperty") == api.MsiGetPropertyLastName());
}
This is all code I'm typing off the top of my head here, but I've done
things this way and it works. You will spend some time creating the
mock MSI api one function at a time. If you already have a class
based wrapper around the MSI API, then you can create an interface for
your wrapper and mock the wrapper instead of the low-level functions.
An alternative to using composition through the _api member in your
CA's implementation is to use a virtual member function that shadows
the global API function. In your test you derive from your production
class and override the virtual member function to fake out the global
API. Roughly, it looks like this:
class MyCustomAction
{
public:
MyCustomAction() { }
virtual ~MyCustomAction() { }
UINT Process(MSIHANDLE session)
{
TCHAR value[1024];
DWORD valueSize = sizeof(value)/sizeof(value[0]);
// note: no use of _api member:
UINT result = MsiGetProperty(session,
_T("customProperty"), &value[0], &valueSize);
return ERROR_OK;
}
protected:
virtual UINT MsiGetProperty(MSIHANDLE session,
LPCTSTR name, LPTSTR value, DWORD *valueSize)
{ return ::MsiGetProperty(session, name, value, valueSize); }
};
class MyCustomActionTester : public MyCustomAction
{
public:
MyCustomActionTester(); // initializes all the fake data
virtual ~MyCustomActionTester() { }
void SetFakeMsiGetPropertyResult(UINT value)
{ _fakeMsiGetPropertyResult = value; }
void SetMsiGetPropertyValueSize(DWORD valueSize)
{ _fakeValueSize = valueSize; }
void SetMsiGetPropertyValue(tstring const &value)
{ _fakeValue = value; }
MSIHANDLE MsiGetPropertyLastSession() const
{ return _lastSession; }
tstring const &MsiGetPropertyLastName() const
{ return _lastName; }
protected:
virtual UINT MsiGetProperty(MSIHANDLE session,
LPCTSTR name, LPTSTR value, DWORD *valueSize)
{
_lastSession = session;
_lastName = name;
_lastValueSize = valueSize;
if (!value && valueSize)
{
*valueSize = _fakeValueSize;
}
else if (value && valueSize)
{
::lstrcpyn(value, _fakeValue.c_str(), *valueSize);
}
return _fakeMsiGetPropertyResult;
}
};
Then your unit test would instantiate MyCustomActionTester and
configure the fake behavior through MyCustomActionTester's members
instead of through FakeApi's members.
The upshot of all of this is that it provides a level of indirection
between your CA and the MSI API so that you can fake out the API as
needed -- no need to create in-memory databases unless you really need
that. No need to call the production MSI API *at all*, avoiding the
whole business of "you can't call that because you're not in a
transaction" problem that you get when you try to half-fake out the
MSI API by providing shim databases.
I've done it both ways and the way I'm outlining here, with either the
FakeApi or the xxxTester class, is much easier to control than with
the shim database.
--
"The Direct3D Graphics Pipeline" -- DirectX 9 draft available for download
<http://www.xmission.com/~legalize/book/download/index.html>
Legalize Adulthood! <http://blogs.xmission.com/legalize/>
date: Tue, 12 Aug 2008 10:34:20 -0700
author: legalize+ (Richard [Microsoft Windows Installer MVP])
|
|