Add traces, add unit tests for IPropertyStorage, and fix the problems

they caught.
This commit is contained in:
Juan Lang 2005-04-11 14:30:24 +00:00 committed by Alexandre Julliard
parent 6d83105054
commit 50794ce787
6 changed files with 295 additions and 43 deletions

View File

@ -22,6 +22,9 @@
#include "windef.h"
#include "winbase.h"
#include "dictionary.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(storage);
struct dictionary_entry
{
@ -36,12 +39,14 @@ struct dictionary
destroyfunc destroy;
void *extra;
struct dictionary_entry *head;
UINT num_entries;
};
struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra)
{
struct dictionary *ret;
TRACE("(%p, %p, %p)\n", c, d, extra);
if (!c)
return NULL;
ret = HeapAlloc(GetProcessHeap(), 0, sizeof(struct dictionary));
@ -51,12 +56,15 @@ struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra)
ret->destroy = d;
ret->extra = extra;
ret->head = NULL;
ret->num_entries = 0;
}
TRACE("returning %p\n", ret);
return ret;
}
void dictionary_destroy(struct dictionary *d)
{
TRACE("(%p)\n", d);
if (d)
{
struct dictionary_entry *p;
@ -74,6 +82,11 @@ void dictionary_destroy(struct dictionary *d)
}
}
UINT dictionary_num_entries(struct dictionary *d)
{
return d ? d->num_entries : 0;
}
/* Returns the address of the pointer to the node containing k. (It returns
* the address of either h->head or the address of the next member of the
* prior node. It's useful when you want to delete.)
@ -101,6 +114,7 @@ void dictionary_insert(struct dictionary *d, const void *k, const void *v)
{
struct dictionary_entry **prior;
TRACE("(%p, %p, %p)\n", d, k, v);
if (!d)
return;
if ((prior = dictionary_find_internal(d, k)))
@ -121,6 +135,7 @@ void dictionary_insert(struct dictionary *d, const void *k, const void *v)
elem->value = (void *)v;
elem->next = d->head;
d->head = elem;
d->num_entries++;
}
}
@ -129,6 +144,7 @@ BOOL dictionary_find(struct dictionary *d, const void *k, void **value)
struct dictionary_entry **prior;
BOOL ret = FALSE;
TRACE("(%p, %p, %p)\n", d, k, value);
if (!d)
return FALSE;
if (!value)
@ -138,6 +154,7 @@ BOOL dictionary_find(struct dictionary *d, const void *k, void **value)
*value = (*prior)->value;
ret = TRUE;
}
TRACE("returning %d (%p)\n", ret, *value);
return ret;
}
@ -145,6 +162,7 @@ void dictionary_remove(struct dictionary *d, const void *k)
{
struct dictionary_entry **prior, *temp;
TRACE("(%p, %p)\n", d, k);
if (!d)
return;
if ((prior = dictionary_find_internal(d, k)))
@ -154,17 +172,20 @@ void dictionary_remove(struct dictionary *d, const void *k)
d->destroy((*prior)->key, (*prior)->value, d->extra);
*prior = (*prior)->next;
HeapFree(GetProcessHeap(), 0, temp);
d->num_entries--;
}
}
void dictionary_enumerate(struct dictionary *d, enumeratefunc e)
void dictionary_enumerate(struct dictionary *d, enumeratefunc e, void *closure)
{
struct dictionary_entry *p;
TRACE("(%p, %p, %p)\n", d, e, closure);
if (!d)
return;
if (!e)
return;
for (p = d->head; p; p = p->next)
e(p->key, p->value, d->extra);
if (!e(p->key, p->value, d->extra, closure))
break;
}

View File

@ -42,7 +42,8 @@ typedef void (*destroyfunc)(void *k, void *v, void *extra);
/* Called for each element in the dictionary. Return FALSE if you don't want
* to enumerate any more.
*/
typedef BOOL (*enumeratefunc)(const void *k, const void *d, void *extra);
typedef BOOL (*enumeratefunc)(const void *k, const void *v, void *extra,
void *closure);
/* Constructs a dictionary, using c as a comparison function for keys.
* If d is not NULL, it will be called whenever an item is about to be removed
@ -56,6 +57,11 @@ struct dictionary *dictionary_create(comparefunc c, destroyfunc d, void *extra);
/* Assumes d is not NULL. */
void dictionary_destroy(struct dictionary *d);
/* Returns how many entries have been stored in the dictionary. If two values
* with the same key are inserted, only one is counted.
*/
UINT dictionary_num_entries(struct dictionary *d);
/* Sets an element with key k and value v to the dictionary. If a value
* already exists with key k, its value is replaced, and the destroyfunc (if
* set) is called for the previous item.
@ -82,6 +88,6 @@ BOOL dictionary_find(struct dictionary *d, const void *k, void **v);
*/
void dictionary_remove(struct dictionary *d, const void *k);
void dictionary_enumerate(struct dictionary *d, enumeratefunc e);
void dictionary_enumerate(struct dictionary *d, enumeratefunc e, void *closure);
#endif /* ndef __DICTIONARY_H__ */

View File

@ -79,6 +79,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(storage);
#define PROPSETHDR_OSVER_KIND_MAC 1
#define PROPSETHDR_OSVER_KIND_WIN32 2
#define CP_UNICODE 1200
/* The format version (and what it implies) is described here:
* http://msdn.microsoft.com/library/en-us/stg/stg/format_version.asp
*/
@ -106,7 +108,7 @@ typedef struct tagPROPERTYSECTIONHEADER
typedef struct tagPROPERTYIDOFFSET
{
DWORD propid;
DWORD dwOffset;
DWORD dwOffset; /* from beginning of section */
} PROPERTYIDOFFSET;
/* Initializes the property storage from the stream (and undoes any uncommitted
@ -209,6 +211,7 @@ static PROPVARIANT *PropertyStorage_FindProperty(PropertyStorage_impl *This,
if (!This)
return NULL;
dictionary_find(This->propid_to_prop, (void *)propid, (void **)&ret);
TRACE("returning %p\n", ret);
return ret;
}
@ -222,6 +225,7 @@ static PROPVARIANT *PropertyStorage_FindPropertyByName(
return NULL;
if (dictionary_find(This->name_to_propid, name, (void **)&propid))
ret = PropertyStorage_FindProperty(This, (PROPID)propid);
TRACE("returning %p\n", ret);
return ret;
}
@ -233,6 +237,7 @@ static LPWSTR PropertyStorage_FindPropertyNameById(PropertyStorage_impl *This,
if (!This)
return NULL;
dictionary_find(This->propid_to_name, (void *)propid, (void **)&ret);
TRACE("returning %p\n", ret);
return ret;
}
@ -246,7 +251,7 @@ static HRESULT WINAPI IPropertyStorage_fnReadMultiple(
PROPVARIANT rgpropvar[])
{
PropertyStorage_impl *This = (PropertyStorage_impl *)iface;
HRESULT hr = S_OK;
HRESULT hr = S_FALSE;
ULONG i;
TRACE("(%p, %ld, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar);
@ -298,6 +303,8 @@ static HRESULT PropertyStorage_StorePropWithId(PropertyStorage_impl *This,
{
PropVariantCopy(prop, propvar);
dictionary_insert(This->propid_to_prop, (void *)propid, prop);
if (propid > This->highestProp)
This->highestProp = propid;
}
else
hr = STG_E_INSUFFICIENTMEMORY;
@ -341,6 +348,9 @@ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple(
PropVariantCopy(prop, &rgpropvar[i]);
else
{
/* Note that I don't do the special cases here that I do below,
* because naming the special PIDs isn't supported.
*/
if (propidNameFirst < PID_FIRST_USABLE ||
propidNameFirst >= PID_MIN_READONLY)
hr = STG_E_INVALIDPARAMETER;
@ -352,11 +362,12 @@ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple(
len * sizeof(WCHAR));
strcpyW(name, rgpspec[i].u.lpwstr);
TRACE("Adding prop name %s, propid %ld\n", debugstr_w(name),
nextId);
dictionary_insert(This->name_to_propid, name,
(void *)nextId);
dictionary_insert(This->propid_to_name, (void *)nextId,
name);
This->highestProp = nextId;
hr = PropertyStorage_StorePropWithId(This, nextId,
&rgpropvar[i]);
}
@ -364,12 +375,38 @@ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple(
}
else
{
/* FIXME: certain propid's have special behavior. E.g., you can't
* set propid 0, and setting PID_BEHAVIOR affects the
* case-sensitivity.
*/
hr = PropertyStorage_StorePropWithId(This, rgpspec[i].u.propid,
&rgpropvar[i]);
switch (rgpspec[i].u.propid)
{
case PID_DICTIONARY:
/* Can't set the dictionary */
hr = STG_E_INVALIDPARAMETER;
break;
case PID_CODEPAGE:
/* Can only set the code page if nothing else has been set */
if (dictionary_num_entries(This->propid_to_prop) == 0 &&
rgpropvar[i].vt == VT_I2)
This->codePage = rgpropvar[i].u.iVal;
else
hr = STG_E_INVALIDPARAMETER;
break;
case PID_LOCALE:
/* Can only set the locale if nothing else has been set */
if (dictionary_num_entries(This->propid_to_prop) == 0 &&
rgpropvar[i].vt == VT_I4)
This->locale = rgpropvar[i].u.lVal;
else
hr = STG_E_INVALIDPARAMETER;
break;
case PID_ILLEGAL:
/* silently ignore like MSDN says */
break;
default:
if (rgpspec[i].u.propid >= PID_MIN_READONLY)
hr = STG_E_INVALIDPARAMETER;
else
hr = PropertyStorage_StorePropWithId(This,
rgpspec[i].u.propid, &rgpropvar[i]);
}
}
}
LeaveCriticalSection(&This->cs);
@ -391,7 +428,7 @@ static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple(
TRACE("(%p, %ld, %p)\n", iface, cpspec, rgpspec);
if (!This)
return E_INVALIDARG;
if (!rgpspec)
if (cpspec && !rgpspec)
return E_INVALIDARG;
if (!(This->grfMode & STGM_READWRITE))
return STG_E_ACCESSDENIED;
@ -409,13 +446,12 @@ static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple(
}
else
{
/* FIXME: certain propid's have special meaning. For example,
* removing propid 0 is supposed to remove the dictionary, and
* removing PID_BEHAVIOR should change this to a case-insensitive
* property set. Unknown "read-only" propid's should be ignored.
*/
dictionary_remove(This->propid_to_prop,
(void *)rgpspec[i].u.propid);
if (rgpspec[i].u.propid >= PID_FIRST_USABLE &&
rgpspec[i].u.propid < PID_MIN_READONLY)
dictionary_remove(This->propid_to_prop,
(void *)rgpspec[i].u.propid);
else
hr = STG_E_INVALIDPARAMETER;
}
}
LeaveCriticalSection(&This->cs);
@ -433,17 +469,13 @@ static HRESULT WINAPI IPropertyStorage_fnReadPropertyNames(
{
PropertyStorage_impl *This = (PropertyStorage_impl *)iface;
ULONG i;
HRESULT hr;
HRESULT hr = S_FALSE;
TRACE("(%p, %ld, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName);
if (!This)
return E_INVALIDARG;
if (cpropid && (!rgpropid || !rglpwstrName))
return E_INVALIDARG;
/* MSDN says S_FALSE is returned if no strings matching rgpropid are found,
* default to that
*/
hr = S_FALSE;
EnterCriticalSection(&This->cs);
for (i = 0; i < cpropid && SUCCEEDED(hr); i++)
{
@ -664,6 +696,11 @@ static int PropertyStorage_PropNameCompare(const void *a, const void *b,
{
PropertyStorage_impl *This = (PropertyStorage_impl *)extra;
TRACE("(%s, %s)\n", debugstr_w(a), debugstr_w(b));
/* FIXME: this assumes property names are always Unicode, but they
* might be ANSI, depending on whether This->grfFlags & PROPSETFLAG_ANSI
* is true.
*/
if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE)
return strcmpW((LPCWSTR)a, (LPCWSTR)b);
else
@ -678,6 +715,7 @@ static void PropertyStorage_PropNameDestroy(void *k, void *d, void *extra)
static int PropertyStorage_PropCompare(const void *a, const void *b,
void *extra)
{
TRACE("(%ld, %ld)\n", (PROPID)a, (PROPID)b);
return (PROPID)a - (PROPID)b;
}
@ -691,6 +729,8 @@ static void PropertyStorage_PropertyDestroy(void *k, void *d, void *extra)
* the entries according to the values of This->codePage and This->locale.
* FIXME: there isn't any checking whether the read property extends past the
* end of the buffer.
* FIXME: this always stores dictionary entries as Unicode, but it should store
* them as ANSI if (This->grfFlags & PROPSETFLAG_ANSI) is true.
*/
static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This,
BYTE *ptr)
@ -699,10 +739,6 @@ static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This,
HRESULT hr = S_OK;
ptr += sizeof(DWORD);
This->name_to_propid = dictionary_create(PropertyStorage_PropNameCompare,
PropertyStorage_PropNameDestroy, This);
This->propid_to_name = dictionary_create(PropertyStorage_PropCompare, NULL,
This);
if (This->name_to_propid && This->propid_to_name)
{
DWORD i;
@ -742,7 +778,7 @@ static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This,
}
ptr += sizeof(DWORD) + cbEntry;
/* Unicode entries are padded to DWORD boundaries */
if (This->codePage == 1200 && cbEntry % sizeof(DWORD))
if (This->codePage == CP_UNICODE && cbEntry % sizeof(DWORD))
ptr += sizeof(DWORD) - (cbEntry % sizeof(DWORD));
}
}
@ -863,6 +899,8 @@ static HRESULT PropertyStorage_ReadHeaderFromStream(IStream *stm,
ULONG count = 0;
HRESULT hr;
assert(stm);
assert(hdr);
hr = IStream_Read(stm, buf, sizeof(buf), &count);
if (SUCCEEDED(hr))
{
@ -896,6 +934,8 @@ static HRESULT PropertyStorage_ReadFmtIdOffsetFromStream(IStream *stm,
ULONG count = 0;
HRESULT hr;
assert(stm);
assert(fmt);
hr = IStream_Read(stm, buf, sizeof(buf), &count);
if (SUCCEEDED(hr))
{
@ -923,6 +963,8 @@ static HRESULT PropertyStorage_ReadSectionHeaderFromStream(IStream *stm,
ULONG count = 0;
HRESULT hr;
assert(stm);
assert(hdr);
hr = IStream_Read(stm, buf, sizeof(buf), &count);
if (SUCCEEDED(hr))
{
@ -962,9 +1004,29 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
This->dirty = FALSE;
This->highestProp = 0;
dictionary_destroy(This->name_to_propid);
This->name_to_propid = NULL;
dictionary_destroy(This->propid_to_name);
This->propid_to_name = NULL;
dictionary_destroy(This->propid_to_prop);
This->name_to_propid = dictionary_create(PropertyStorage_PropNameCompare,
PropertyStorage_PropNameDestroy, This);
if (!This->name_to_propid)
{
hr = STG_E_INSUFFICIENTMEMORY;
goto end;
}
This->propid_to_name = dictionary_create(PropertyStorage_PropCompare, NULL,
This);
if (!This->propid_to_name)
{
hr = STG_E_INSUFFICIENTMEMORY;
goto end;
}
This->propid_to_prop = dictionary_create(PropertyStorage_PropCompare,
PropertyStorage_PropertyDestroy, This);
if (!This->propid_to_prop)
{
hr = STG_E_INSUFFICIENTMEMORY;
goto end;
}
hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME);
if (FAILED(hr))
goto end;
@ -1047,9 +1109,6 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
sizeof(PROPERTYSECTIONHEADER), &count);
if (FAILED(hr))
goto end;
dictionary_destroy(This->propid_to_prop);
This->propid_to_prop = dictionary_create(PropertyStorage_PropCompare,
PropertyStorage_PropertyDestroy, This);
for (i = 0; SUCCEEDED(hr) && i < sectionHdr.cProperties; i++)
{
PROPERTYIDOFFSET *idOffset = (PROPERTYIDOFFSET *)(buf +
@ -1064,7 +1123,7 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
idOffset->propid < PID_MIN_READONLY && idOffset->propid >
This->highestProp)
This->highestProp = idOffset->propid;
if (idOffset->propid == 0)
if (idOffset->propid == PID_DICTIONARY)
{
/* Don't read the dictionary yet, its entries depend on the
* code page. Just store the offset so we know to read it
@ -1083,10 +1142,6 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER) +
sizeof(DWORD))))
{
/* FIXME: the PID_CODEPAGE and PID_LOCALE special cases
* aren't really needed, just look them up in
* propid_to_prop when needed
*/
switch(idOffset->propid)
{
case PID_CODEPAGE:
@ -1117,7 +1172,7 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
if (This->grfFlags & PROPSETFLAG_ANSI)
This->codePage = GetACP();
else
This->codePage = 1200;
This->codePage = CP_UNICODE;
}
if (!This->locale)
This->locale = LOCALE_SYSTEM_DEFAULT;
@ -1128,10 +1183,18 @@ static HRESULT PropertyStorage_ReadFromStream(IPropertyStorage *iface)
end:
HeapFree(GetProcessHeap(), 0, buf);
if (FAILED(hr))
{
dictionary_destroy(This->name_to_propid);
This->name_to_propid = NULL;
dictionary_destroy(This->propid_to_name);
This->propid_to_name = NULL;
dictionary_destroy(This->propid_to_prop);
This->propid_to_prop = NULL;
}
return hr;
}
static HRESULT PropertyStorage_WriteToStream(IPropertyStorage *iface)
{
FIXME("\n");

View File

@ -2,5 +2,6 @@ Makefile
marshal.ok
moniker.ok
propvariant.ok
stg_prop.ok
storage32.ok
testlist.c

View File

@ -10,6 +10,7 @@ CTESTS = \
marshal.c \
moniker.c \
propvariant.c \
stg_prop.c \
storage32.c
@MAKE_TEST_RULES@

160
dlls/ole32/tests/stg_prop.c Normal file
View File

@ -0,0 +1,160 @@
/* IPropertyStorage unit tests
* Copyright 2005 Juan Lang
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdio.h>
#define COBJMACROS
#include "objbase.h"
#include "wine/test.h"
#ifdef NONAMELESSUNION
# define U(x) (x).u
#else
# define U(x) (x)
#endif
/* FIXME: this creates an ANSI storage, need try to find conditions under which
* Unicode translation fails
*/
static void testProps(void)
{
static const WCHAR szDot[] = { '.',0 };
static const WCHAR szPrefix[] = { 's','t','g',0 };
static const WCHAR propName[] = { 'p','r','o','p',0 };
WCHAR filename[MAX_PATH];
HRESULT hr;
IStorage *storage = NULL;
IPropertySetStorage *propSetStorage = NULL;
IPropertyStorage *propertyStorage = NULL;
PROPSPEC spec;
PROPVARIANT var;
if(!GetTempFileNameW(szDot, szPrefix, 0, filename))
return;
DeleteFileW(filename);
hr = StgCreateDocfile(filename,
STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE, 0, &storage);
ok(SUCCEEDED(hr), "StgCreateDocfile failed: 0x%08lx\n", hr);
hr = StgCreatePropSetStg(storage, 0, &propSetStorage);
ok(SUCCEEDED(hr), "StgCreatePropSetStg failed: 0x%08lx\n", hr);
hr = IPropertySetStorage_Create(propSetStorage,
&FMTID_SummaryInformation, NULL, PROPSETFLAG_ANSI,
STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE,
&propertyStorage);
ok(SUCCEEDED(hr), "QI -> IPropertyStorage failed: 0x%08lx\n", hr);
hr = IPropertyStorage_WriteMultiple(propertyStorage, 0, NULL, NULL, 0);
ok(SUCCEEDED(hr), "WriteMultiple with 0 args failed: 0x%08lx\n", hr);
hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, NULL, NULL, 0);
ok(hr == E_INVALIDARG, "Expected E_INVALIDARG, got 0x%08lx\n", hr);
/* test setting one that I can't set */
spec.ulKind = PRSPEC_PROPID;
U(spec).propid = PID_DICTIONARY;
PropVariantClear(&var);
var.vt = VT_I4;
U(var).lVal = 1;
hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var, 0);
ok(hr == STG_E_INVALIDPARAMETER,
"Expected STG_E_INVALIDPARAMETER, got 0x%08lx\n", hr);
/* test setting one by name with an invalid propidNameFirst */
spec.ulKind = PRSPEC_LPWSTR;
U(spec).lpwstr = (LPOLESTR)propName;
hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var,
PID_DICTIONARY);
ok(hr == STG_E_INVALIDPARAMETER,
"Expected STG_E_INVALIDPARAMETER, got 0x%08lx\n", hr);
/* test setting behavior (case-sensitive) */
spec.ulKind = PRSPEC_PROPID;
U(spec).propid = PID_BEHAVIOR;
U(var).lVal = 1;
hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var, 0);
ok(hr == STG_E_INVALIDPARAMETER,
"Expected STG_E_INVALIDPARAMETER, got 0x%08lx\n", hr);
/* set one by value.. */
spec.ulKind = PRSPEC_PROPID;
U(spec).propid = PID_FIRST_USABLE;
U(var).lVal = 1;
hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var, 0);
ok(SUCCEEDED(hr), "WriteMultiple failed: 0x%08lx\n", hr);
/* finally, set one by name */
spec.ulKind = PRSPEC_LPWSTR;
U(spec).lpwstr = (LPOLESTR)propName;
U(var).lVal = 2;
hr = IPropertyStorage_WriteMultiple(propertyStorage, 1, &spec, &var,
PID_FIRST_USABLE);
ok(SUCCEEDED(hr), "WriteMultiple failed: 0x%08lx\n", hr);
/* check reading */
hr = IPropertyStorage_ReadMultiple(propertyStorage, 0, NULL, NULL);
ok(SUCCEEDED(hr), "ReadMultiple with 0 args failed: 0x%08lx\n", hr);
hr = IPropertyStorage_ReadMultiple(propertyStorage, 1, NULL, NULL);
ok(hr == E_INVALIDARG, "Expected E_INVALIDARG, got 0x%08lx\n", hr);
/* read by propid */
spec.ulKind = PRSPEC_PROPID;
U(spec).propid = PID_FIRST_USABLE;
hr = IPropertyStorage_ReadMultiple(propertyStorage, 1, &spec, &var);
ok(SUCCEEDED(hr), "ReadMultiple failed: 0x%08lx\n", hr);
ok(var.vt == VT_I4 && U(var).lVal == 1,
"Didn't get expected type or value for property (got type %d, value %ld)\n",
var.vt, U(var).lVal);
/* read by name */
spec.ulKind = PRSPEC_LPWSTR;
U(spec).lpwstr = (LPOLESTR)propName;
hr = IPropertyStorage_ReadMultiple(propertyStorage, 1, &spec, &var);
ok(SUCCEEDED(hr), "ReadMultiple failed: 0x%08lx\n", hr);
ok(var.vt == VT_I4 && U(var).lVal == 2,
"Didn't get expected type or value for property (got type %d, value %ld)\n",
var.vt, U(var).lVal);
/* check deleting */
hr = IPropertyStorage_DeleteMultiple(propertyStorage, 0, NULL);
ok(SUCCEEDED(hr), "DeleteMultiple with 0 args failed: 0x%08lx\n", hr);
hr = IPropertyStorage_DeleteMultiple(propertyStorage, 1, NULL);
ok(hr == E_INVALIDARG, "Expected E_INVALIDARG, got 0x%08lx\n", hr);
/* contrary to what the docs say, you can't delete the dictionary */
spec.ulKind = PRSPEC_PROPID;
U(spec).propid = PID_DICTIONARY;
hr = IPropertyStorage_DeleteMultiple(propertyStorage, 1, &spec);
ok(hr == STG_E_INVALIDPARAMETER,
"Expected STG_E_INVALIDPARAMETER, got 0x%08lx\n", hr);
/* now delete the first value.. */
U(spec).propid = PID_FIRST_USABLE;
hr = IPropertyStorage_DeleteMultiple(propertyStorage, 1, &spec);
ok(SUCCEEDED(hr), "DeleteMultiple failed: 0x%08lx\n", hr);
/* and check that it's no longer readable */
hr = IPropertyStorage_ReadMultiple(propertyStorage, 1, &spec, &var);
ok(hr == S_FALSE, "Expected S_FALSE, got 0x%08lx\n", hr);
IPropertyStorage_Release(propertyStorage);
IPropertySetStorage_Release(propSetStorage);
IStorage_Release(storage);
DeleteFileW(filename);
}
START_TEST(stg_prop)
{
testProps();
}