add msi wine regression test
authorGed Murphy <gedmurphy@reactos.org>
Tue, 1 Aug 2006 22:48:23 +0000 (22:48 +0000)
committerGed Murphy <gedmurphy@reactos.org>
Tue, 1 Aug 2006 22:48:23 +0000 (22:48 +0000)
svn path=/trunk/; revision=23410

reactos/regtests/winetests/directory.rbuild
reactos/regtests/winetests/msi/db.c [new file with mode: 0644]
reactos/regtests/winetests/msi/format.c [new file with mode: 0644]
reactos/regtests/winetests/msi/install.c [new file with mode: 0644]
reactos/regtests/winetests/msi/msi.c [new file with mode: 0644]
reactos/regtests/winetests/msi/msi.rbuild [new file with mode: 0644]
reactos/regtests/winetests/msi/package.c [new file with mode: 0644]
reactos/regtests/winetests/msi/record.c [new file with mode: 0644]
reactos/regtests/winetests/msi/suminfo.c [new file with mode: 0644]
reactos/regtests/winetests/msi/testlist.c [new file with mode: 0644]

index 4238917..4369297 100644 (file)
@@ -20,6 +20,9 @@
 <directory name="lz32">
         <xi:include href="lz32/lz32.rbuild" />
 </directory>
+<directory name="msi">
+       <xi:include href="msi/msi.rbuild" />
+</directory>
 <directory name="msvcrt">
        <xi:include href="msvcrt/msvcrt.rbuild" />
 </directory>
diff --git a/reactos/regtests/winetests/msi/db.c b/reactos/regtests/winetests/msi/db.c
new file mode 100644 (file)
index 0000000..3143f0f
--- /dev/null
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (C) 2005 Mike McCormack for CodeWeavers
+ *
+ * A test program for MSI database files.
+ *
+ * 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 <stdio.h>
+
+#include <windows.h>
+#include <msi.h>
+#include <msiquery.h>
+
+#include "wine/test.h"
+#include "wine/windef.h"
+
+static const char *msifile = "winetest.msi";
+
+static void test_msidatabase(void)
+{
+    MSIHANDLE hdb = 0;
+    UINT res;
+
+    DeleteFile(msifile);
+
+    /* create an empty database */
+    res = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb );
+    ok( res == ERROR_SUCCESS , "Failed to create database\n" );
+
+    res = MsiDatabaseCommit( hdb );
+    ok( res == ERROR_SUCCESS , "Failed to commit database\n" );
+
+    res = MsiCloseHandle( hdb );
+    ok( res == ERROR_SUCCESS , "Failed to close database\n" );
+
+    res = DeleteFile( msifile );
+    ok( res == TRUE, "Failed to delete database\n" );
+}
+
+static UINT do_query(MSIHANDLE hdb, const char *query, MSIHANDLE *phrec)
+{
+    MSIHANDLE hview = 0;
+    UINT r, ret;
+
+    /* open a select query */
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    if (r != ERROR_SUCCESS)
+        return r;
+    r = MsiViewExecute(hview, 0);
+    if (r != ERROR_SUCCESS)
+        return r;
+    ret = MsiViewFetch(hview, phrec);
+    r = MsiViewClose(hview);
+    if (r != ERROR_SUCCESS)
+        return r;
+    r = MsiCloseHandle(hview);
+    if (r != ERROR_SUCCESS)
+        return r;
+    return ret;
+}
+
+static void test_msiinsert(void)
+{
+    MSIHANDLE hdb = 0, hview = 0, hrec = 0;
+    UINT r;
+    const char *query;
+    char buf[80];
+    DWORD sz;
+
+    DeleteFile(msifile);
+
+    /* just MsiOpenDatabase should not create a file */
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
+    ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
+
+    /* create a table */
+    query = "CREATE TABLE `phone` ( "
+            "`id` INT, `name` CHAR(32), `number` CHAR(32) "
+            "PRIMARY KEY `id`)";
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
+    r = MsiViewExecute(hview, 0);
+    ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
+    r = MsiViewClose(hview);
+    ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
+    r = MsiCloseHandle(hview);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    /* insert a value into it */
+    query = "INSERT INTO `phone` ( `id`, `name`, `number` )"
+        "VALUES('1', 'Abe', '8675309')";
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
+    r = MsiViewExecute(hview, 0);
+    ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
+    r = MsiViewClose(hview);
+    ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
+    r = MsiCloseHandle(hview);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    query = "SELECT * FROM `phone` WHERE `id` = 1";
+    r = do_query(hdb, query, &hrec);
+    ok(r == ERROR_SUCCESS, "MsiViewFetch failed\n");
+
+    /* check the record contains what we put in it */
+    r = MsiRecordGetFieldCount(hrec);
+    ok(r == 3, "record count wrong\n");
+
+    todo_wine {
+    r = MsiRecordIsNull(hrec, 0);
+    ok(r == FALSE, "field 0 not null\n");
+    }
+
+    r = MsiRecordGetInteger(hrec, 1);
+    ok(r == 1, "field 1 contents wrong\n");
+    sz = sizeof buf;
+    r = MsiRecordGetString(hrec, 2, buf, &sz);
+    ok(r == ERROR_SUCCESS, "field 2 content fetch failed\n");
+    ok(!strcmp(buf,"Abe"), "field 2 content incorrect\n");
+    sz = sizeof buf;
+    r = MsiRecordGetString(hrec, 3, buf, &sz);
+    ok(r == ERROR_SUCCESS, "field 3 content fetch failed\n");
+    ok(!strcmp(buf,"8675309"), "field 3 content incorrect\n");
+
+    r = MsiCloseHandle(hrec);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    /* open a select query */
+    hrec = 100;
+    query = "SELECT * FROM `phone` WHERE `id` >= 10";
+    r = do_query(hdb, query, &hrec);
+    ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
+    ok(hrec == 0, "hrec should be null\n");
+
+    r = MsiCloseHandle(hrec);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    query = "SELECT * FROM `phone` WHERE `id` < 0";
+    r = do_query(hdb, query, &hrec);
+    ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
+
+    query = "SELECT * FROM `phone` WHERE `id` <= 0";
+    r = do_query(hdb, query, &hrec);
+    ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
+
+    query = "SELECT * FROM `phone` WHERE `id` <> 1";
+    r = do_query(hdb, query, &hrec);
+    ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
+
+    query = "SELECT * FROM `phone` WHERE `id` > 10";
+    r = do_query(hdb, query, &hrec);
+    ok(r == ERROR_NO_MORE_ITEMS, "MsiViewFetch failed\n");
+
+    todo_wine {
+    /* now try a few bad INSERT xqueries */
+    query = "INSERT INTO `phone` ( `id`, `name`, `number` )"
+        "VALUES(?, ?)";
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    ok(r == ERROR_BAD_QUERY_SYNTAX, "MsiDatabaseOpenView failed\n");
+
+    if (r == ERROR_SUCCESS)
+        r = MsiCloseHandle(hview);
+    }
+
+    /* construct a record to insert */
+    hrec = MsiCreateRecord(4);
+    r = MsiRecordSetInteger(hrec, 1, 2);
+    ok(r == ERROR_SUCCESS, "MsiRecordSetInteger failed\n");
+    r = MsiRecordSetString(hrec, 2, "Adam");
+    ok(r == ERROR_SUCCESS, "MsiRecordSetString failed\n");
+    r = MsiRecordSetString(hrec, 3, "96905305");
+    ok(r == ERROR_SUCCESS, "MsiRecordSetString failed\n");
+
+    /* insert another value, using a record and wildcards */
+    query = "INSERT INTO `phone` ( `id`, `name`, `number` )"
+        "VALUES(?, ?, ?)";
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
+
+    if (r == ERROR_SUCCESS)
+    {
+        r = MsiViewExecute(hview, hrec);
+        ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
+        r = MsiViewClose(hview);
+        ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
+        r = MsiCloseHandle(hview);
+        ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+    }
+    r = MsiCloseHandle(hrec);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    r = MsiViewFetch(0, NULL);
+    ok(r == ERROR_INVALID_PARAMETER, "MsiViewFetch failed\n");
+
+    r = MsiDatabaseCommit(hdb);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseCommit failed\n");
+
+    r = MsiCloseHandle(hdb);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    r = DeleteFile(msifile);
+    ok(r == TRUE, "file didn't exist after commit\n");
+}
+
+typedef UINT (WINAPI *fnMsiDecomposeDescriptorA)(LPCSTR, LPCSTR, LPSTR, LPSTR, DWORD *);
+static fnMsiDecomposeDescriptorA pMsiDecomposeDescriptorA;
+
+static void test_msidecomposedesc(void)
+{
+    char prod[MAX_FEATURE_CHARS+1], comp[MAX_FEATURE_CHARS+1], feature[MAX_FEATURE_CHARS+1];
+    const char *desc;
+    UINT r;
+    DWORD len;
+    HMODULE hmod;
+
+    hmod = GetModuleHandle("msi.dll");
+    if (!hmod)
+        return;
+    pMsiDecomposeDescriptorA = (fnMsiDecomposeDescriptorA)
+        GetProcAddress(hmod, "MsiDecomposeDescriptorA");
+    if (!pMsiDecomposeDescriptorA)
+        return;
+
+    /* test a valid feature descriptor */
+    desc = "']gAVn-}f(ZXfeAR6.jiFollowTheWhiteRabbit>3w2x^IGfe?CxI5heAvk.";
+    len = 0;
+    r = pMsiDecomposeDescriptorA(desc, prod, feature, comp, &len);
+    ok(r == ERROR_SUCCESS, "returned an error\n");
+    ok(len == strlen(desc), "length was wrong\n");
+    ok(strcmp(prod,"{90110409-6000-11D3-8CFE-0150048383C9}")==0, "product wrong\n");
+    ok(strcmp(feature,"FollowTheWhiteRabbit")==0, "feature wrong\n");
+    ok(strcmp(comp,"{A7CD68DB-EF74-49C8-FBB2-A7C463B2AC24}")==0,"component wrong\n");
+
+    /* test an invalid feature descriptor with too many characters */
+    desc = "']gAVn-}f(ZXfeAR6.ji"
+           "ThisWillFailIfTheresMoreThanAGuidsChars>"
+           "3w2x^IGfe?CxI5heAvk.";
+    len = 0;
+    r = pMsiDecomposeDescriptorA(desc, prod, feature, comp, &len);
+    ok(r == ERROR_INVALID_PARAMETER, "returned wrong error\n");
+
+    /*
+     * Test a valid feature descriptor with the
+     * maximum number of characters and some trailing characters.
+     */
+    desc = "']gAVn-}f(ZXfeAR6.ji"
+           "ThisWillWorkIfTheresLTEThanAGuidsChars>"
+           "3w2x^IGfe?CxI5heAvk."
+           "extra";
+    len = 0;
+    r = pMsiDecomposeDescriptorA(desc, prod, feature, comp, &len);
+    ok(r == ERROR_SUCCESS, "returned wrong error\n");
+    ok(len == (strlen(desc) - strlen("extra")), "length wrong\n");
+
+    len = 0;
+    r = pMsiDecomposeDescriptorA(desc, prod, feature, NULL, &len);
+    ok(r == ERROR_SUCCESS, "returned wrong error\n");
+    ok(len == (strlen(desc) - strlen("extra")), "length wrong\n");
+
+    len = 0;
+    r = pMsiDecomposeDescriptorA(desc, prod, NULL, NULL, &len);
+    ok(r == ERROR_SUCCESS, "returned wrong error\n");
+    ok(len == (strlen(desc) - strlen("extra")), "length wrong\n");
+
+    len = 0;
+    r = pMsiDecomposeDescriptorA(desc, NULL, NULL, NULL, &len);
+    ok(r == ERROR_SUCCESS, "returned wrong error\n");
+    ok(len == (strlen(desc) - strlen("extra")), "length wrong\n");
+
+    len = 0;
+    r = pMsiDecomposeDescriptorA(NULL, NULL, NULL, NULL, &len);
+    ok(r == ERROR_INVALID_PARAMETER, "returned wrong error\n");
+    ok(len == 0, "length wrong\n");
+}
+
+static UINT try_query_param( MSIHANDLE hdb, LPCSTR szQuery, MSIHANDLE hrec )
+{
+    MSIHANDLE htab = 0;
+    UINT res;
+
+    res = MsiDatabaseOpenView( hdb, szQuery, &htab );
+    if(res == ERROR_SUCCESS )
+    {
+        UINT r;
+
+        r = MsiViewExecute( htab, hrec );
+        if(r != ERROR_SUCCESS )
+            res = r;
+
+        r = MsiViewClose( htab );
+        if(r != ERROR_SUCCESS )
+            res = r;
+
+        r = MsiCloseHandle( htab );
+        if(r != ERROR_SUCCESS )
+            res = r;
+    }
+    return res;
+}
+
+static UINT try_query( MSIHANDLE hdb, LPCSTR szQuery )
+{
+    return try_query_param( hdb, szQuery, 0 );
+}
+
+static UINT try_insert_query( MSIHANDLE hdb, LPCSTR szQuery )
+{
+    MSIHANDLE hrec = 0;
+    UINT r;
+
+    hrec = MsiCreateRecord( 1 );
+    MsiRecordSetString( hrec, 1, "Hello");
+
+    r = try_query_param( hdb, szQuery, hrec );
+
+    MsiCloseHandle( hrec );
+    return r;
+}
+
+static void test_msibadqueries(void)
+{
+    MSIHANDLE hdb = 0;
+    UINT r;
+
+    DeleteFile(msifile);
+
+    /* just MsiOpenDatabase should not create a file */
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
+    ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
+
+    r = MsiDatabaseCommit( hdb );
+    ok(r == ERROR_SUCCESS , "Failed to commit database\n");
+
+    r = MsiCloseHandle( hdb );
+    ok(r == ERROR_SUCCESS , "Failed to close database\n");
+
+    /* open it readonly */
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_READONLY, &hdb );
+    ok(r == ERROR_SUCCESS , "Failed to open database r/o\n");
+
+    /* add a table to it */
+    r = try_query( hdb, "select * from _Tables");
+    ok(r == ERROR_SUCCESS , "query 1 failed\n");
+
+    r = MsiCloseHandle( hdb );
+    ok(r == ERROR_SUCCESS , "Failed to close database r/o\n");
+
+    /* open it read/write */
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_TRANSACT, &hdb );
+    ok(r == ERROR_SUCCESS , "Failed to open database r/w\n");
+
+    /* a bunch of test queries that fail with the native MSI */
+
+    r = try_query( hdb, "CREATE TABLE");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2a return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a`");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2b return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` ()");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2c return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b`)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2d return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) )");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2e return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2f return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2g return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2h return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2i return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY 'b')");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2j return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY `b')");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2k return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY `b')");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2l return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHA(72) NOT NULL PRIMARY KEY `b`)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2m return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(-1) NOT NULL PRIMARY KEY `b`)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2n return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(720) NOT NULL PRIMARY KEY `b`)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2o return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL KEY `b`)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2p return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`` CHAR(72) NOT NULL PRIMARY KEY `b`)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "invalid query 2p return code\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY `b`)");
+    ok(r == ERROR_SUCCESS , "valid query 2z failed\n");
+
+    r = try_query( hdb, "CREATE TABLE `a` (`b` CHAR(72) NOT NULL PRIMARY KEY `b`)");
+    ok(r == ERROR_BAD_QUERY_SYNTAX , "created same table again\n");
+
+    r = try_query( hdb, "CREATE TABLE `aa` (`b` CHAR(72) NOT NULL, `c` "
+          "CHAR(72), `d` CHAR(255) NOT NULL LOCALIZABLE PRIMARY KEY `b`)");
+    ok(r == ERROR_SUCCESS , "query 4 failed\n");
+
+    r = MsiDatabaseCommit( hdb );
+    ok(r == ERROR_SUCCESS , "Failed to commit database after write\n");
+
+    r = try_query( hdb, "CREATE TABLE `blah` (`foo` CHAR(72) NOT NULL "
+                          "PRIMARY KEY `foo`)");
+    ok(r == ERROR_SUCCESS , "query 4 failed\n");
+
+    r = try_insert_query( hdb, "insert into a  ( `b` ) VALUES ( ? )");
+    ok(r == ERROR_SUCCESS , "failed to insert record in db\n");
+
+    r = MsiDatabaseCommit( hdb );
+    ok(r == ERROR_SUCCESS , "Failed to commit database after write\n");
+
+    r = try_query( hdb, "CREATE TABLE `boo` (`foo` CHAR(72) NOT NULL "
+                          "PRIMARY KEY `ba`)");
+    ok(r != ERROR_SUCCESS , "query 5 succeeded\n");
+
+    r = try_query( hdb,"CREATE TABLE `bee` (`foo` CHAR(72) NOT NULL )");
+    ok(r != ERROR_SUCCESS , "query 6 succeeded\n");
+
+    r = try_query( hdb, "CREATE TABLE `temp` (`t` CHAR(72) NOT NULL "
+                          "PRIMARY KEY `t`)");
+    ok(r == ERROR_SUCCESS , "query 7 failed\n");
+
+    r = try_query( hdb, "CREATE TABLE `c` (`b` CHAR NOT NULL PRIMARY KEY `b`)");
+    ok(r == ERROR_SUCCESS , "query 8 failed\n");
+
+    r = MsiCloseHandle( hdb );
+    ok(r == ERROR_SUCCESS , "Failed to close database transact\n");
+
+    r = DeleteFile( msifile );
+    ok(r == TRUE, "file didn't exist after commit\n");
+}
+
+static UINT run_query( MSIHANDLE hdb, const char *query )
+{
+    MSIHANDLE hview = 0;
+    UINT r;
+
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    if( r != ERROR_SUCCESS )
+        return r;
+
+    r = MsiViewExecute(hview, 0);
+    if( r == ERROR_SUCCESS )
+        r = MsiViewClose(hview);
+    MsiCloseHandle(hview);
+    return r;
+}
+
+static void test_viewmodify(void)
+{
+    MSIHANDLE hdb = 0, hview = 0, hrec = 0;
+    UINT r;
+    const char *query;
+    char buffer[0x100];
+    DWORD sz;
+
+    DeleteFile(msifile);
+
+    /* just MsiOpenDatabase should not create a file */
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
+    ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
+
+    query = "CREATE TABLE `phone` ( "
+            "`id` INT, `name` CHAR(32), `number` CHAR(32) "
+            "PRIMARY KEY `id`)";
+    r = run_query( hdb, query );
+    ok(r == ERROR_SUCCESS, "query failed\n");
+
+    /* check what the error function reports without doing anything */
+    sz = 0;
+    /* passing NULL as the 3rd param make function to crash on older platforms */
+    r = MsiViewGetError( 0, NULL, &sz );
+    ok(r == MSIDBERROR_INVALIDARG, "MsiViewGetError return\n");
+
+    /* open a view */
+    query = "SELECT * FROM `phone`";
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
+
+    /* see what happens with a good hview and bad args */
+    r = MsiViewGetError( hview, NULL, NULL );
+    ok(r == MSIDBERROR_INVALIDARG || r == MSIDBERROR_NOERROR,
+       "MsiViewGetError returns %u (expected -3)\n", r);
+    r = MsiViewGetError( hview, buffer, NULL );
+    ok(r == MSIDBERROR_INVALIDARG, "MsiViewGetError return\n");
+
+    /* see what happens with a zero length buffer */
+    sz = 0;
+    buffer[0] = 'x';
+    r = MsiViewGetError( hview, buffer, &sz );
+    ok(r == MSIDBERROR_MOREDATA, "MsiViewGetError return\n");
+    ok(buffer[0] == 'x', "buffer cleared\n");
+    ok(sz == 0, "size not zero\n");
+
+    /* ok this one is strange */
+    sz = 0;
+    r = MsiViewGetError( hview, NULL, &sz );
+    ok(r == MSIDBERROR_NOERROR, "MsiViewGetError return\n");
+    ok(sz == 0, "size not zero\n");
+
+    /* see if it really has an error */
+    sz = sizeof buffer;
+    buffer[0] = 'x';
+    r = MsiViewGetError( hview, buffer, &sz );
+    ok(r == MSIDBERROR_NOERROR, "MsiViewGetError return\n");
+    ok(buffer[0] == 0, "buffer not cleared\n");
+    ok(sz == 0, "size not zero\n");
+
+    r = MsiViewExecute(hview, 0);
+    ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
+
+    /* try some invalid records */
+    r = MsiViewModify(hview, MSIMODIFY_INSERT, 0 );
+    ok(r == ERROR_INVALID_HANDLE, "MsiViewModify failed\n");
+    r = MsiViewModify(hview, -1, 0 );
+    ok(r == ERROR_INVALID_HANDLE, "MsiViewModify failed\n");
+
+    /* try an small record */
+    hrec = MsiCreateRecord(1);
+    r = MsiViewModify(hview, -1, hrec );
+    ok(r == ERROR_INVALID_DATA, "MsiViewModify failed\n");
+
+    r = MsiCloseHandle(hrec);
+    ok(r == ERROR_SUCCESS, "failed to close record\n");
+
+    /* insert a valid record */
+    hrec = MsiCreateRecord(3);
+
+    r = MsiRecordSetInteger(hrec, 2, 1);
+    ok(r == ERROR_SUCCESS, "failed to set integer\n");
+    r = MsiRecordSetString(hrec, 2, "bob");
+    ok(r == ERROR_SUCCESS, "failed to set integer\n");
+    r = MsiRecordSetString(hrec, 3, "7654321");
+    ok(r == ERROR_SUCCESS, "failed to set integer\n");
+
+    r = MsiViewExecute(hview, 0);
+    ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
+    r = MsiViewModify(hview, MSIMODIFY_INSERT_TEMPORARY, hrec );
+    ok(r == ERROR_SUCCESS, "MsiViewModify failed\n");
+
+    /* insert the same thing again */
+    r = MsiViewExecute(hview, 0);
+    ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
+
+    /* should fail ... */
+    todo_wine {
+    r = MsiViewModify(hview, MSIMODIFY_INSERT_TEMPORARY, hrec );
+    ok(r == ERROR_FUNCTION_FAILED, "MsiViewModify failed\n");
+    }
+
+    r = MsiCloseHandle(hrec);
+    ok(r == ERROR_SUCCESS, "failed to close record\n");
+
+    r = MsiViewClose(hview);
+    ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
+    r = MsiCloseHandle(hview);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    r = MsiCloseHandle( hdb );
+    ok(r == ERROR_SUCCESS, "MsiOpenDatabase close failed\n");
+}
+
+static MSIHANDLE create_db(void)
+{
+    MSIHANDLE hdb = 0;
+    UINT res;
+
+    DeleteFile(msifile);
+
+    /* create an empty database */
+    res = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb );
+    ok( res == ERROR_SUCCESS , "Failed to create database\n" );
+    if( res != ERROR_SUCCESS )
+        return hdb;
+
+    res = MsiDatabaseCommit( hdb );
+    ok( res == ERROR_SUCCESS , "Failed to commit database\n" );
+
+    return hdb;
+}
+
+static void test_getcolinfo(void)
+{
+    MSIHANDLE hdb, hview = 0, rec = 0;
+    UINT r;
+    DWORD sz;
+    char buffer[0x20];
+
+    /* create an empty db */
+    hdb = create_db();
+    ok( hdb, "failed to create db\n");
+
+    /* tables should be present */
+    r = MsiDatabaseOpenView(hdb, "select * from _Tables", &hview);
+    ok( r == ERROR_SUCCESS, "failed to open query\n");
+
+    r = MsiViewExecute(hview, 0);
+    ok( r == ERROR_SUCCESS, "failed to execute query\n");
+
+    /* check that NAMES works */
+    rec = 0;
+    r = MsiViewGetColumnInfo( hview, MSICOLINFO_NAMES, &rec );
+    ok( r == ERROR_SUCCESS, "failed to get names\n");
+    sz = sizeof buffer;
+    r = MsiRecordGetString(rec, 1, buffer, &sz );
+    ok( r == ERROR_SUCCESS, "failed to get string\n");
+    ok( !strcmp(buffer,"Name"), "_Tables has wrong column name\n");
+    r = MsiCloseHandle( rec );
+    ok( r == ERROR_SUCCESS, "failed to close record handle\n");
+
+    /* check that TYPES works */
+    rec = 0;
+    r = MsiViewGetColumnInfo( hview, MSICOLINFO_TYPES, &rec );
+    ok( r == ERROR_SUCCESS, "failed to get names\n");
+    sz = sizeof buffer;
+    r = MsiRecordGetString(rec, 1, buffer, &sz );
+    ok( r == ERROR_SUCCESS, "failed to get string\n");
+    ok( !strcmp(buffer,"s64"), "_Tables has wrong column type\n");
+    r = MsiCloseHandle( rec );
+    ok( r == ERROR_SUCCESS, "failed to close record handle\n");
+
+    /* check that invalid values fail */
+    rec = 0;
+    r = MsiViewGetColumnInfo( hview, 100, &rec );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error code\n");
+    ok( rec == 0, "returned a record\n");
+
+    r = MsiViewGetColumnInfo( hview, MSICOLINFO_TYPES, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error code\n");
+
+    r = MsiViewGetColumnInfo( 0, MSICOLINFO_TYPES, &rec );
+    ok( r == ERROR_INVALID_HANDLE, "wrong error code\n");
+
+    r = MsiViewClose(hview);
+    ok( r == ERROR_SUCCESS, "failed to close view\n");
+    r = MsiCloseHandle(hview);
+    ok( r == ERROR_SUCCESS, "failed to close view handle\n");
+    r = MsiCloseHandle(hdb);
+    ok( r == ERROR_SUCCESS, "failed to close database\n");
+}
+
+static MSIHANDLE get_column_info(MSIHANDLE hdb, const char *query, MSICOLINFO type)
+{
+    MSIHANDLE hview = 0, rec = 0;
+    UINT r;
+
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    if( r != ERROR_SUCCESS )
+        return r;
+
+    r = MsiViewExecute(hview, 0);
+    if( r == ERROR_SUCCESS )
+    {
+        MsiViewGetColumnInfo( hview, type, &rec );
+        MsiViewClose(hview);
+    }
+    MsiCloseHandle(hview);
+    return rec;
+}
+
+static UINT get_columns_table_type(MSIHANDLE hdb, const char *table, UINT field)
+{
+    MSIHANDLE hview = 0, rec = 0;
+    UINT r, type = 0;
+    char query[0x100];
+
+    sprintf(query, "select * from `_Columns` where  `Table` = '%s'", table );
+
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    if( r != ERROR_SUCCESS )
+        return r;
+
+    r = MsiViewExecute(hview, 0);
+    if( r == ERROR_SUCCESS )
+    {
+        while (1)
+        {
+            r = MsiViewFetch( hview, &rec );
+            if( r != ERROR_SUCCESS)
+                break;
+            r = MsiRecordGetInteger( rec, 2 );
+            if (r == field)
+                type = MsiRecordGetInteger( rec, 4 );
+            MsiCloseHandle( rec );
+        }
+
+        MsiViewClose(hview);
+    }
+    MsiCloseHandle(hview);
+    return type;
+}
+
+static BOOL check_record( MSIHANDLE rec, UINT field, LPSTR val )
+{
+    CHAR buffer[0x20];
+    UINT r;
+    DWORD sz;
+
+    sz = sizeof buffer;
+    r = MsiRecordGetString( rec, field, buffer, &sz );
+    return (r == ERROR_SUCCESS ) && !strcmp(val, buffer);
+}
+
+static void test_viewgetcolumninfo(void)
+{
+    MSIHANDLE hdb = 0, rec;
+    UINT r;
+
+    hdb = create_db();
+    ok( hdb, "failed to create db\n");
+
+    r = run_query( hdb,
+            "CREATE TABLE `Properties` "
+            "( `Property` CHAR(255), `Value` CHAR(1)  PRIMARY KEY `Property`)" );
+    ok( r == ERROR_SUCCESS , "Failed to create table\n" );
+
+    /* check the column types */
+    rec = get_column_info( hdb, "select * from `Properties`", MSICOLINFO_TYPES );
+    ok( rec, "failed to get column info record\n" );
+
+    ok( check_record( rec, 1, "S255"), "wrong record type\n");
+    ok( check_record( rec, 2, "S1"), "wrong record type\n");
+
+    MsiCloseHandle( rec );
+
+    /* check the type in _Columns */
+    ok( 0x3dff == get_columns_table_type(hdb, "Properties", 1 ), "_columns table wrong\n");
+    ok( 0x1d01 == get_columns_table_type(hdb, "Properties", 2 ), "_columns table wrong\n");
+
+    /* now try the names */
+    rec = get_column_info( hdb, "select * from `Properties`", MSICOLINFO_NAMES );
+    ok( rec, "failed to get column info record\n" );
+
+    ok( check_record( rec, 1, "Property"), "wrong record type\n");
+    ok( check_record( rec, 2, "Value"), "wrong record type\n");
+
+    MsiCloseHandle( rec );
+
+    r = run_query( hdb,
+            "CREATE TABLE `Binary` "
+            "( `Name` CHAR(255), `Data` OBJECT  PRIMARY KEY `Name`)" );
+    ok( r == ERROR_SUCCESS , "Failed to create table\n" );
+
+    /* check the column types */
+    rec = get_column_info( hdb, "select * from `Binary`", MSICOLINFO_TYPES );
+    ok( rec, "failed to get column info record\n" );
+
+    ok( check_record( rec, 1, "S255"), "wrong record type\n");
+    ok( check_record( rec, 2, "V0"), "wrong record type\n");
+
+    MsiCloseHandle( rec );
+
+    /* check the type in _Columns */
+    ok( 0x3dff == get_columns_table_type(hdb, "Binary", 1 ), "_columns table wrong\n");
+    ok( 0x1900 == get_columns_table_type(hdb, "Binary", 2 ), "_columns table wrong\n");
+
+    /* now try the names */
+    rec = get_column_info( hdb, "select * from `Binary`", MSICOLINFO_NAMES );
+    ok( rec, "failed to get column info record\n" );
+
+    ok( check_record( rec, 1, "Name"), "wrong record type\n");
+    ok( check_record( rec, 2, "Data"), "wrong record type\n");
+    MsiCloseHandle( rec );
+
+    r = run_query( hdb,
+            "CREATE TABLE `UIText` "
+            "( `Key` CHAR(72) NOT NULL, `Text` CHAR(255) LOCALIZABLE PRIMARY KEY `Key`)" );
+    ok( r == ERROR_SUCCESS , "Failed to create table\n" );
+
+    ok( 0x2d48 == get_columns_table_type(hdb, "UIText", 1 ), "_columns table wrong\n");
+    ok( 0x1fff == get_columns_table_type(hdb, "UIText", 2 ), "_columns table wrong\n");
+
+    rec = get_column_info( hdb, "select * from `UIText`", MSICOLINFO_NAMES );
+    ok( rec, "failed to get column info record\n" );
+    ok( check_record( rec, 1, "Key"), "wrong record type\n");
+    ok( check_record( rec, 2, "Text"), "wrong record type\n");
+    MsiCloseHandle( rec );
+
+    rec = get_column_info( hdb, "select * from `UIText`", MSICOLINFO_TYPES );
+    ok( rec, "failed to get column info record\n" );
+    ok( check_record( rec, 1, "s72"), "wrong record type\n");
+    ok( check_record( rec, 2, "L255"), "wrong record type\n");
+    MsiCloseHandle( rec );
+
+    MsiCloseHandle( hdb );
+}
+
+static void test_msiexport(void)
+{
+    MSIHANDLE hdb = 0, hview = 0;
+    UINT r;
+    const char *query;
+    char path[MAX_PATH];
+    const char file[] = "phone.txt";
+    HANDLE handle;
+    char buffer[0x100];
+    DWORD length;
+    const char expected[] =
+        "id\tname\tnumber\r\n"
+        "I2\tS32\tS32\r\n"
+        "phone\tid\r\n"
+        "1\tAbe\t8675309\r\n";
+
+    DeleteFile(msifile);
+
+    /* just MsiOpenDatabase should not create a file */
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
+    ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
+
+    /* create a table */
+    query = "CREATE TABLE `phone` ( "
+            "`id` INT, `name` CHAR(32), `number` CHAR(32) "
+            "PRIMARY KEY `id`)";
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
+    r = MsiViewExecute(hview, 0);
+    ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
+    r = MsiViewClose(hview);
+    ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
+    r = MsiCloseHandle(hview);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    /* insert a value into it */
+    query = "INSERT INTO `phone` ( `id`, `name`, `number` )"
+        "VALUES('1', 'Abe', '8675309')";
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
+    r = MsiViewExecute(hview, 0);
+    ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
+    r = MsiViewClose(hview);
+    ok(r == ERROR_SUCCESS, "MsiViewClose failed\n");
+    r = MsiCloseHandle(hview);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    GetCurrentDirectory(MAX_PATH, path);
+
+    todo_wine {
+    r = MsiDatabaseExport(hdb, "phone", path, file);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseExport failed\n");
+
+    MsiCloseHandle(hdb);
+
+    lstrcat(path, "\\");
+    lstrcat(path, file);
+
+    /* check the data that was written */
+    length = 0;
+    memset(buffer, 0, sizeof buffer);
+    handle = CreateFile(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
+    if (handle != INVALID_HANDLE_VALUE)
+    {
+        ReadFile(handle, buffer, sizeof buffer, &length, NULL);
+        CloseHandle(handle);
+        DeleteFile(path);
+    }
+    else
+        ok(0, "failed to open file %s\n", path);
+
+    ok( length == strlen(expected), "length of data wrong\n");
+    ok( !lstrcmp(buffer, expected), "data doesn't match\n");
+    }
+    DeleteFile(msifile);
+}
+
+static void test_longstrings(void)
+{
+    const char insert_query[] = 
+        "INSERT INTO `strings` ( `id`, `val` ) VALUES('1', 'Z')";
+    char *str;
+    MSIHANDLE hdb = 0, hview = 0, hrec = 0;
+    DWORD len;
+    UINT r;
+    const DWORD STRING_LENGTH = 0x10005;
+
+    DeleteFile(msifile);
+    /* just MsiOpenDatabase should not create a file */
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
+    ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
+
+    /* create a table */
+    r = try_query( hdb, 
+        "CREATE TABLE `strings` ( `id` INT, `val` CHAR(0) PRIMARY KEY `id`)");
+    ok(r == ERROR_SUCCESS, "query failed\n");
+
+    /* try a insert a very long string */
+    str = HeapAlloc(GetProcessHeap(), 0, STRING_LENGTH+sizeof insert_query);
+    len = strchr(insert_query, 'Z') - insert_query;
+    strcpy(str, insert_query);
+    memset(str+len, 'Z', STRING_LENGTH);
+    strcpy(str+len+STRING_LENGTH, insert_query+len+1);
+    r = try_query( hdb, str );
+    ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
+
+    HeapFree(GetProcessHeap(), 0, str);
+
+    MsiDatabaseCommit(hdb);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseCommit failed\n");
+    MsiCloseHandle(hdb);
+
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_READONLY, &hdb);
+    ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
+
+    r = MsiDatabaseOpenView(hdb, "select * from `strings` where `id` = 1", &hview);
+    ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
+
+    r = MsiViewExecute(hview, 0);
+    ok(r == ERROR_SUCCESS, "MsiViewExecute failed\n");
+
+    r = MsiViewFetch(hview, &hrec);
+    ok(r == ERROR_SUCCESS, "MsiViewFetch failed\n");
+
+    MsiCloseHandle(hview);
+
+    r = MsiRecordGetString(hrec, 2, NULL, &len);
+    ok(r == ERROR_SUCCESS, "MsiViewFetch failed\n");
+    todo_wine {
+    ok(len == STRING_LENGTH, "string length wrong\n");
+    }
+
+    MsiCloseHandle(hrec);
+    MsiCloseHandle(hdb);
+    DeleteFile(msifile);
+}
+static void test_streamtable(void)
+{
+    MSIHANDLE hdb = 0, rec;
+    UINT r;
+
+    hdb = create_db();
+    ok( hdb, "failed to create db\n");
+
+    r = run_query( hdb,
+            "CREATE TABLE `Properties` "
+            "( `Property` CHAR(255), `Value` CHAR(1)  PRIMARY KEY `Property`)" );
+    ok( r == ERROR_SUCCESS , "Failed to create table\n" );
+
+    /* check the column types */
+    rec = get_column_info( hdb, "select * from `_Streams`", MSICOLINFO_TYPES );
+    ok( rec, "failed to get column info record\n" );
+
+    todo_wine {
+    ok( check_record( rec, 1, "s62"), "wrong record type\n");
+    ok( check_record( rec, 2, "V0"), "wrong record type\n");
+    }
+
+    MsiCloseHandle( rec );
+
+    /* now try the names */
+    rec = get_column_info( hdb, "select * from `_Streams`", MSICOLINFO_NAMES );
+    ok( rec, "failed to get column info record\n" );
+
+    todo_wine {
+    ok( check_record( rec, 1, "Name"), "wrong record type\n");
+    ok( check_record( rec, 2, "Data"), "wrong record type\n");
+    }
+
+    MsiCloseHandle( rec );
+    MsiCloseHandle( hdb );
+    DeleteFile(msifile);
+}
+
+static void test_where(void)
+{
+    MSIHANDLE hdb = 0, rec;
+    LPCSTR query;
+    UINT r;
+
+    hdb = create_db();
+    ok( hdb, "failed to create db\n");
+
+    r = run_query( hdb,
+            "CREATE TABLE `Media` ("
+            "`DiskId` SHORT NOT NULL, "
+            "`LastSequence` LONG, "
+            "`DiskPrompt` CHAR(64) LOCALIZABLE, "
+            "`Cabinet` CHAR(255), "
+            "`VolumeLabel` CHAR(32), "
+            "`Source` CHAR(72) "
+            "PRIMARY KEY `DiskId`)" );
+    ok( r == S_OK, "cannot create Media table: %d\n", r );
+
+    r = run_query( hdb, "INSERT INTO `Media` "
+            "( `DiskId`, `LastSequence`, `DiskPrompt`, `Cabinet`, `VolumeLabel`, `Source` ) "
+            "VALUES ( 1, 0, '', 'zero.cab', '', '' )" );
+    ok( r == S_OK, "cannot add file to the Media table: %d\n", r );
+
+    r = run_query( hdb, "INSERT INTO `Media` "
+            "( `DiskId`, `LastSequence`, `DiskPrompt`, `Cabinet`, `VolumeLabel`, `Source` ) "
+            "VALUES ( 2, 1, '', 'one.cab', '', '' )" );
+    ok( r == S_OK, "cannot add file to the Media table: %d\n", r );
+
+    r = run_query( hdb, "INSERT INTO `Media` "
+            "( `DiskId`, `LastSequence`, `DiskPrompt`, `Cabinet`, `VolumeLabel`, `Source` ) "
+            "VALUES ( 3, 2, '', 'two.cab', '', '' )" );
+    ok( r == S_OK, "cannot add file to the Media table: %d\n", r );
+
+    query = "SELECT * FROM `Media`";
+    r = do_query(hdb, query, &rec);
+    ok(r == ERROR_SUCCESS, "MsiViewFetch failed: %d\n", r);
+    ok( check_record( rec, 4, "zero.cab"), "wrong cabinet\n");
+    MsiCloseHandle( rec );
+
+    query = "SELECT * FROM `Media` WHERE `LastSequence` >= 1";
+    r = do_query(hdb, query, &rec);
+    ok(r == ERROR_SUCCESS, "MsiViewFetch failed: %d\n", r);
+    ok( check_record( rec, 4, "one.cab"), "wrong cabinet\n");
+
+    r = MsiRecordGetInteger(rec, 1);
+    ok( 2 == r, "field wrong\n");
+    r = MsiRecordGetInteger(rec, 2);
+    ok( 1 == r, "field wrong\n");
+
+    MsiCloseHandle( rec );
+
+    MsiCloseHandle( hdb );
+    DeleteFile(msifile);
+}
+
+START_TEST(db)
+{
+    test_msidatabase();
+    test_msiinsert();
+    test_msidecomposedesc();
+    test_msibadqueries();
+    test_viewmodify();
+    test_viewgetcolumninfo();
+    test_getcolinfo();
+    test_msiexport();
+    test_longstrings();
+    test_streamtable();
+    test_where();
+}
diff --git a/reactos/regtests/winetests/msi/format.c b/reactos/regtests/winetests/msi/format.c
new file mode 100644 (file)
index 0000000..a187c65
--- /dev/null
@@ -0,0 +1,1369 @@
+/*
+ * Copyright (C) 2005 Mike McCormack for CodeWeavers
+ * Copyright (C) 2005 Aric Stewart for CodeWeavers
+ *
+ * A test program for MSI record formatting
+ *
+ * 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 <stdio.h>
+#include <windows.h>
+#include <msi.h>
+#include <msiquery.h>
+
+#include "wine/test.h"
+#include "wine/windef.h"
+
+static MSIHANDLE helper_createpackage( const char *szName )
+{
+    MSIHANDLE hdb = 0;
+    UINT res;
+    CHAR szPackage[10];
+    MSIHANDLE hPackage;
+    MSIHANDLE suminfo;
+
+    DeleteFile(szName);
+
+    /* create an empty database */
+    res = MsiOpenDatabase(szName, MSIDBOPEN_CREATE, &hdb );
+    ok( res == ERROR_SUCCESS , "Failed to create database\n" );
+
+    res = MsiDatabaseCommit( hdb );
+    ok( res == ERROR_SUCCESS , "Failed to commit database\n" );
+
+    /* build summmary info */
+    res = MsiGetSummaryInformation(hdb, NULL, 7, &suminfo);
+    ok( res == ERROR_SUCCESS , "Failed to open summaryinfo\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,2, VT_LPSTR, 0,NULL,
+                        "Installation Database");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,3, VT_LPSTR, 0,NULL,
+                        "Installation Database");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,4, VT_LPSTR, 0,NULL,
+                        "Wine Hackers");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,7, VT_LPSTR, 0,NULL,
+                    ";1033");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,9, VT_LPSTR, 0,NULL,
+                    "{913B8D18-FBB6-4CAC-A239-C74C11E3FA74}");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo, 14, VT_I4, 100, NULL, NULL);
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo, 15, VT_I4, 0, NULL, NULL);
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoPersist(suminfo);
+    ok( res == ERROR_SUCCESS , "Failed to make summary info persist\n" );
+
+    res = MsiCloseHandle( suminfo);
+    ok( res == ERROR_SUCCESS , "Failed to close suminfo\n" );
+
+    sprintf(szPackage,"#%li",(DWORD)hdb);
+    res = MsiOpenPackage(szPackage,&hPackage);
+    ok( res == ERROR_SUCCESS , "Failed to open package\n" );
+
+    res = MsiCloseHandle( hdb );
+    ok( res == ERROR_SUCCESS , "Failed to close database\n" );
+
+    return hPackage;
+}
+
+static void test_createpackage(void)
+{
+    static const CHAR filename[] = "winetest.msi";
+    MSIHANDLE hPackage = 0;
+    UINT res;
+
+    hPackage = helper_createpackage( filename );
+    ok ( hPackage != 0, " Failed to create package\n");
+
+    res = MsiCloseHandle( hPackage);
+    ok( res == ERROR_SUCCESS , "Failed to close package\n" );
+
+    DeleteFile( filename );
+}
+
+static void test_formatrecord(void)
+{
+    char buffer[100];
+    MSIHANDLE hrec;
+    UINT r;
+    DWORD sz;
+
+    r = MsiFormatRecord(0, 0, NULL, NULL );
+    ok( r == ERROR_INVALID_HANDLE, "wrong error\n");
+
+    hrec = MsiCreateRecord(4);
+    ok( hrec, "failed to create record\n");
+
+    /* format an empty record */
+    r = MsiFormatRecord(0, hrec, NULL, NULL );
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    buffer[0] = 'x';
+    buffer[1] = 0;
+    sz=0;
+    r = MsiFormatRecord(0, hrec, buffer+1, &sz);
+    ok( r == ERROR_MORE_DATA && buffer[0] == 'x', "format failed measuring with buffer\n");
+    ok( sz == 16, "size wrong\n");
+    sz=100;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed with empty buffer\n");
+    ok( sz == 16, "size wrong\n");
+    ok( 0 == strcmp(buffer,"1:  2:  3:  4:  "), "wrong output\n");
+
+    r = MsiCloseHandle(hrec);
+    ok(r==ERROR_SUCCESS, "Unable to close record\n");
+
+    hrec = MsiCreateRecord(6);
+    ok( hrec, "failed to create record\n");
+
+    sz = 100;
+    buffer[0] = 'x';
+    buffer[1] = 0;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed with empty buffer\n");
+    ok( sz == 24, "size wrong\n");
+    ok( 0 == strcmp(buffer,"1:  2:  3:  4:  5:  6:  "), "wrong output\n");
+
+
+    /* format a format string with everything else empty */
+    r = MsiRecordSetString(hrec, 0, "%1");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    r = MsiFormatRecord(0, hrec, NULL, NULL );
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    r = MsiFormatRecord(0, hrec, buffer, NULL);
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error\n");
+
+    sz = 123;
+    r = MsiFormatRecord(0, hrec, NULL, &sz);
+    ok( r == ERROR_SUCCESS, "format failed with empty buffer\n");
+    ok( sz == 2, "size wrong (%li)\n",sz);
+    sz = sizeof buffer;
+    buffer[0] = 'x';
+    buffer[1] = 0;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed with empty buffer\n");
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,"%1"), "wrong output\n");
+
+    /* make the buffer too small */
+    sz = 0;
+    buffer[0] = 'x';
+    buffer[1] = 0;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_MORE_DATA, "format failed with empty buffer\n");
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,"x"), "wrong output\n");
+
+    /* make the buffer a little bit bigger */
+    sz = 1;
+    buffer[0] = 'x';
+    buffer[1] = 0;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_MORE_DATA, "format failed with empty buffer\n");
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output (%s)\n",buffer);
+
+    /* and again */
+    sz = 2;
+    buffer[0] = 'x';
+    buffer[1] = 0;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_MORE_DATA, "format failed with empty buffer\n");
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,"%"), "wrong output\n");
+
+    /* and again */
+    sz = 3;
+    buffer[0] = 'x';
+    buffer[1] = 0;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed with empty buffer\n");
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,"%1"), "wrong output\n");
+
+    /* now try a real format string */
+    r = MsiRecordSetString(hrec, 0, "[1]");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+
+    /* now put something in the first field */
+    r = MsiRecordSetString(hrec, 1, "boo hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 7, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo hoo"), "wrong output\n");
+
+    /* now put something in the first field */
+    r = MsiRecordSetString(hrec, 0, "[1] [2]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 7, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo hoo"), "wrong output\n");
+
+
+    /* empty string */
+    r = MsiRecordSetString(hrec, 0, "");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 30, "size wrong %li\n",sz);
+    ok( 0 == strcmp(buffer,"1: boo 2: hoo 3:  4:  5:  6:  "), 
+                    "wrong output(%s)\n",buffer);
+
+    /* play games with recursive lookups */
+    r = MsiRecordSetString(hrec, 0, "[[1]] [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 7, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"hey hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[[1]] [2]");
+    r = MsiRecordSetString(hrec, 1, "[2]");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 9, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"[[2]] hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[[[3]]] [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 7, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"hey hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiCloseHandle(hrec);
+    ok(r==ERROR_SUCCESS, "Unable to close record\n");
+    hrec = MsiCreateRecord(12);
+    ok( hrec, "failed to create record\n");
+
+    r = MsiRecordSetString(hrec, 0, "[[3][1]] [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 7, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"big hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[[3][4][1]] [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 7, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"big hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[[3][[4]][1]] [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 10, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"[1[]2] hey"), "wrong output (%s)\n",buffer);
+
+    /* incorrect  formats */
+    r = MsiRecordSetString(hrec, 0, "[[[3][[4]][1]] [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 18, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"[[[3][[4]][1]] [2]"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[[3][[4]][1]] [2]]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 11, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"[1[]2] hey]"), "wrong output (%s)\n",buffer);
+
+
+    /* play games with {} */
+
+    r = MsiRecordSetString(hrec, 0, "{[3][1]} [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 6, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"12 hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[{[3][1]}] [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 8, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"[12] hey"), "wrong output (%s)\n",buffer);
+
+
+    r = MsiRecordSetString(hrec, 0, "{test} [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 10, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"{test} hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "{[test]} [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 12, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"{[test]} hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "{[1][2][3][4]} [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 4, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer," hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "{[1][2][3][dummy]} [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 18, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"{2hey1[dummy]} hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "{[1][2][3][4][dummy]} [2]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 18, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"{2hey1[dummy]} hey"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "{{[1][2]}[3][4][dummy]}");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine{
+    ok( sz == 16, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"{{2hey}1[dummy]}"), "wrong output (%s)\n",buffer);
+    }
+
+    r = MsiRecordSetString(hrec, 0, "{{[1][2]}[3]{[4][dummy]}}");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "hey");
+    r = MsiRecordSetString(hrec, 3, "1");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    todo_wine{
+    ok( sz == 0, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,""), "wrong output (%s)\n",buffer);
+    }
+
+    r = MsiRecordSetString(hrec, 0, "{{[1][2]}[3]} {[1][2]}");
+    r = MsiRecordSetString(hrec, 1, "1");
+    r = MsiRecordSetString(hrec, 2, "2");
+    r = MsiRecordSetString(hrec, 3, "3");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    todo_wine{
+    ok( sz == 12, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"{{12}3} {12}"), "wrong output (%s)\n",buffer);
+    }
+
+    r = MsiRecordSetString(hrec, 0, "{[1][2]} {{[1][2]}[3]} {[1][2]}");
+    r = MsiRecordSetString(hrec, 1, "1");
+    r = MsiRecordSetString(hrec, 2, "2");
+    r = MsiRecordSetString(hrec, 3, "3");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    todo_wine{
+    ok( sz == 15, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"12 {{12}3} {12}"), "wrong output (%s)\n",buffer);
+    }
+
+    r = MsiRecordSetString(hrec, 0, "{[4]}{[1][2]} {{[1][2]}[3]} {[1][2]}");
+    r = MsiRecordSetString(hrec, 1, "1");
+    r = MsiRecordSetString(hrec, 2, "2");
+    r = MsiRecordSetString(hrec, 3, "3");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    todo_wine{
+    ok( sz == 15, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"12 {{12}3} {12}"), "wrong output (%s)\n",buffer);
+    }
+
+
+    r = MsiRecordSetString(hrec, 0, "{blah} {[4]}{[1][2]} {{[1][2]}[3]} {[1][2]}");
+    r = MsiRecordSetString(hrec, 1, "1");
+    r = MsiRecordSetString(hrec, 2, "2");
+    r = MsiRecordSetString(hrec, 3, "3");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    todo_wine{
+    ok( sz == 22, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"{blah} 12 {{12}3} {12}"), "wrong output (%s)\n",buffer);
+    }
+
+    r = MsiRecordSetString(hrec, 0, "{{[1]}[2]} {[4]}{[1][2]}");
+    r = MsiRecordSetString(hrec, 1, "1");
+    r = MsiRecordSetString(hrec, 2, "2");
+    r = MsiRecordSetString(hrec, 3, "3");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    todo_wine{
+    ok( sz == 13, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"{{1}2} {}{12}"), "wrong output (%s)\n",buffer);
+    }
+
+    r = MsiRecordSetString(hrec, 0, "{{[1]}} {[4]}{[1][2]}");
+    r = MsiRecordSetString(hrec, 1, "1");
+    r = MsiRecordSetString(hrec, 2, "2");
+    r = MsiRecordSetString(hrec, 3, "3");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    todo_wine{
+    ok( sz == 3, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer," 12"), "wrong output (%s)\n",buffer);
+    }
+
+    r = MsiRecordSetString(hrec, 0, "{{{[1]}} {[4]}{[1][2]}");
+    r = MsiRecordSetString(hrec, 1, "1");
+    r = MsiRecordSetString(hrec, 2, "2");
+    r = MsiRecordSetString(hrec, 3, "3");
+    r = MsiRecordSetString(hrec, 4, NULL);
+    r = MsiRecordSetString(hrec, 12, "big");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    todo_wine{
+    ok( sz == 3, "size wrong,(%li)\n",sz);
+    ok( 0 == strcmp(buffer," 12"), "wrong output (%s)\n",buffer);
+    }
+    
+    /* now put play games with escaping */
+    r = MsiRecordSetString(hrec, 0, "[1] [2] [\\3asdf]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 16, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo hoo [\\3asdf]"), "wrong output\n");
+
+    /* now put play games with escaping */
+    r = MsiRecordSetString(hrec, 0, "[1] [2] [\\x]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 12, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo hoo [\\x]"), "wrong output\n");
+
+    /* now try other formats without a package */
+    r = MsiRecordSetString(hrec, 0, "[1] [2] [property]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 18, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo hoo [property]"), "wrong output\n");
+
+    r = MsiRecordSetString(hrec, 0, "[1] [~] [2]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 11, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo [~] hoo"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[1]");
+    r = MsiRecordSetInteger(hrec, 1, 123456);
+    ok( r == ERROR_SUCCESS, "set integer failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    todo_wine{
+    ok( sz == 6, "size wrong\n");
+    ok( 0 == strcmp(buffer,"123456"), "wrong output (%s)\n",buffer);
+    }
+
+    r = MsiRecordSetString(hrec, 0, "[~]");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[~]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[]");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    /* MsiFormatRecord doesn't seem to handle a negative too well */
+    r = MsiRecordSetString(hrec, 0, "[-1]");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 4, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[-1]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[]}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 4, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{[]}"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[0]");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[0]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[100]");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[1] [2]}");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 7, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo hoo"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{foo}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 5, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{foo}"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{boo [1]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 7, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo hoo"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{[1]}}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{ {[1]}}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( 0 == strcmp(buffer," {hoo}"), "wrong output\n");
+    ok( sz == 6, "size wrong\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{[1]} }");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 8, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{{hoo} }"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{ [1]}}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{[1] }}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{a}{b}{c }{ d}{any text}}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{{a} }");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 6, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{{a} }"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{a} {b}}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{a} b}}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{a b}}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{ }");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{ }"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, " {{a}}}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer," }"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{ almost {{ any }} text }}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 8, "size wrong\n");
+    ok( 0 == strcmp(buffer," text }}"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{ } { hidden ][ [ }}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 0, "size wrong\n");
+    ok( 0 == strcmp(buffer,""), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[ 1]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 4, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[ 1]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[01]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"hoo"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{test}} [01");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 4, "size wrong\n");
+    ok( 0 == strcmp(buffer," [01"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[\\[]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 4, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[\\[]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[foo]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 5, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[foo]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[01.]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 5, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[01.]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    SetEnvironmentVariable("FOO", "BAR");
+    r = MsiRecordSetString(hrec, 0, "[%FOO]");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 6, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[%FOO]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{[1]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 6, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{{hoo}"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{ {[1]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 8, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{{ {hoo}"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{ {[1]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 8, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{{ {hoo}"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{ {{[1]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 9, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{{ {{hoo}"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[1]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 4, "size wrong\n");
+    ok( 0 == strcmp(buffer,"hoo}"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{{ {{a}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 7, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{{ {{a}"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{{ {{a}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 7, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{{ {{a}"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "0{1{2{3{4[1]5}6}7}8}9");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 19, "size wrong\n");
+    ok( 0 == strcmp(buffer,"01{2{3{4hoo56}7}8}9"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "0{1{2[1]3}4");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 9, "size wrong\n");
+    ok( 0 == strcmp(buffer,"01{2hoo34"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "0{1{2[1]3}4");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 9, "size wrong\n");
+    ok( 0 == strcmp(buffer,"01{2hoo34"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[1.} [1]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 9, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{[1.} hoo"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[{[1]}]}");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "foo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 9, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{[{[1]}]}"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[1][}");
+    r = MsiRecordSetString(hrec, 1, "2");
+    r = MsiRecordSetString(hrec, 2, "foo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,"2["), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[1]");
+    r = MsiRecordSetString(hrec, 1, "[2]");
+    r = MsiRecordSetString(hrec, 2, "foo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[2]"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "[{{boo}}1]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[1]"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "[{{boo}}1]");
+    r = MsiRecordSetString(hrec, 0, "[1{{boo}}]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[1]"), "wrong output\n");
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[1]{{boo} }}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 11, "size wrong\n");
+    ok( 0 == strcmp(buffer,"hoo{{boo }}"), "wrong output\n");
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[1{{boo}}]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 12, "size wrong: got %lu, expected 12\n", sz);
+    ok( 0 == strcmp(buffer,"{[1{{boo}}]}"), "wrong output: got %s, expected [1]\n", buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    todo_wine {
+    r = MsiRecordSetString(hrec, 0, "{{[1]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 6, "size wrong: got %lu, expected 3\n", sz);
+    ok( 0 == strcmp(buffer,"{{hoo}"), "wrong output: got %s, expected [1]\n", buffer);
+    }
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[1{{bo}o}}]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 13, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{[1{{bo}o}}]}"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[1{{b{o}o}}]}");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 14, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{[1{{b{o}o}}]}"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{ {[1]}");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 5, "size wrong\n");
+    ok( 0 == strcmp(buffer," {hoo"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    /* {} inside a substitution does strange things... */
+    r = MsiRecordSetString(hrec, 0, "[[1]{}]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 5, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[[1]]"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[[1]{}[1]]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 6, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[[1]2]"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[a[1]b[1]c{}d[1]e]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 14, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[a[1]b[1]cd2e]"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[a[1]b");
+    r = MsiRecordSetString(hrec, 1, "2");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 6, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[a[1]b"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "a[1]b]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 4, "size wrong\n");
+    ok( 0 == strcmp(buffer,"a2b]"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "]a[1]b");
+    r = MsiRecordSetString(hrec, 1, "2");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 4, "size wrong\n");
+    ok( 0 == strcmp(buffer,"]a2b"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "]a[1]b");
+    r = MsiRecordSetString(hrec, 1, "2");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 4, "size wrong\n");
+    ok( 0 == strcmp(buffer,"]a2b"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "\\[1]");
+    r = MsiRecordSetString(hrec, 1, "2");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,"\\2"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "\\{[1]}");
+    r = MsiRecordSetString(hrec, 1, "2");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,"\\2"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "a{b[1]c}d");
+    r = MsiRecordSetString(hrec, 1, NULL);
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 2, "size wrong\n");
+    ok( 0 == strcmp(buffer,"ad"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{a[0]b}");
+    r = MsiRecordSetString(hrec, 1, "foo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 9, "size wrong\n");
+    ok( 0 == strcmp(buffer,"a{a[0]b}b"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[foo]");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 5, "size wrong\n");
+    ok( 0 == strcmp(buffer,"[foo]"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "{[1][-1][1]}");
+    r = MsiRecordSetString(hrec, 1, "foo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(0, hrec, buffer, &sz);
+    ok( sz == 12, "size wrong\n");
+    ok( 0 == strcmp(buffer,"{foo[-1]foo}"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    MsiCloseHandle( hrec );
+}
+
+static void test_formatrecord_package(void)
+{
+    static const CHAR filename[] = "winetest.msi";
+    char buffer[100];
+    MSIHANDLE hrec;
+    MSIHANDLE package;
+    UINT r;
+    DWORD sz=100;
+
+    package = helper_createpackage( filename );
+    ok(package!=0, "Unable to create package\n");
+
+    hrec = MsiCreateRecord(12);
+    ok( hrec, "failed to create record\n");
+
+    r = MsiFormatRecord(package, 0, NULL, NULL );
+    ok( r == ERROR_INVALID_HANDLE, "wrong error\n");
+
+    r = MsiFormatRecord(package, hrec, NULL, NULL );
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec,0,NULL);
+    r = MsiRecordSetString(hrec,1,NULL);
+    r = MsiRecordSetString(hrec,2,NULL);
+    r = MsiRecordSetString(hrec,3,NULL);
+    r = MsiRecordSetString(hrec,4,NULL);
+    r = MsiRecordSetString(hrec,5,NULL);
+    r = MsiRecordSetString(hrec,6,NULL);
+    r = MsiRecordSetString(hrec,7,NULL);
+    r = MsiRecordSetString(hrec,8,NULL);
+    r = MsiRecordSetString(hrec,9,NULL);
+    r = MsiRecordSetString(hrec,10,NULL);
+    r = MsiRecordSetString(hrec,11,NULL);
+    r = MsiRecordSetString(hrec,12,NULL);
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed with empty buffer (%i)\n",r);
+    ok( sz == 51, "size wrong (%li)\n",sz);
+    ok( 0 == strcmp(buffer,"1:  2:  3:  4:  5:  6:  7:  8:  9:  10:  11:  12:  "), "wrong output(%s)\n",buffer);
+
+    /* now put play games with escaping */
+    r = MsiRecordSetString(hrec, 0, "[1] [2] [\\3asdf]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 9, "size wrong(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"boo hoo 3"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[1] [2] [\\x]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 9, "size wrong(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"boo hoo x"), "wrong output (%s)\n",buffer);
+
+    /* null characters */
+    r = MsiRecordSetString(hrec, 0, "[1] [~] [2]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 9, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo "), "wrong output\n");
+
+    /* properties */
+    r = MsiSetProperty(package,"dummy","Bork");
+    ok( r == ERROR_SUCCESS, "set property failed\n");
+    r = MsiRecordSetString(hrec, 0, "[1] [dummy] [2]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 12, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo Bork hoo"), "wrong output\n");
+
+    r = MsiRecordSetString(hrec, 0, "[1] [invalid] [2]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 8, "size wrong\n");
+    ok( 0 == strcmp(buffer,"boo  hoo"), "wrong output\n");
+
+
+    /* nesting tests */
+    r = MsiSetProperty(package,"dummya","foo");
+    r = MsiSetProperty(package,"dummyb","baa");
+    r = MsiSetProperty(package,"adummyc","whoa");
+    ok( r == ERROR_SUCCESS, "set property failed\n");
+    r = MsiRecordSetString(hrec, 0, "[dummy[1]] [dummy[2]] [[1]dummy[3]]");
+    r = MsiRecordSetString(hrec, 1, "a");
+    r = MsiRecordSetString(hrec, 2, "b");
+    r = MsiRecordSetString(hrec, 3, "c");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 12, "size wrong(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"foo baa whoa"), "wrong output (%s)\n",buffer);
+
+
+    r = MsiSetProperty(package,"dummya","1");
+    r = MsiSetProperty(package,"dummyb","[2]");
+    ok( r == ERROR_SUCCESS, "set property failed\n");
+    r = MsiRecordSetString(hrec, 0, "[dummya] [[dummya]] [dummyb]");
+    r = MsiRecordSetString(hrec, 1, "aaa");
+    r = MsiRecordSetString(hrec, 2, "bbb");
+    r = MsiRecordSetString(hrec, 3, "ccc");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 9, "size wrong(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"1 [1] [2]"), "wrong output (%s)\n",buffer);
+
+    r = MsiSetProperty(package,"dummya","1");
+    r = MsiSetProperty(package,"dummyb","a");
+    r = MsiSetProperty(package,"dummyc","\\blath");
+    r = MsiSetProperty(package,"dummyd","[\\blath]");
+    ok( r == ERROR_SUCCESS, "set property failed\n");
+    r = MsiRecordSetString(hrec, 0, "[dummyc] [[dummyc]] [dummy[dummyb]]");
+    r = MsiRecordSetString(hrec, 1, "aaa");
+    r = MsiRecordSetString(hrec, 2, "bbb");
+    r = MsiRecordSetString(hrec, 3, "ccc");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 10, "size wrong(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"\\blath b 1"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[1] [2] [[\\3asdf]]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    r = MsiRecordSetString(hrec, 3, "yeah");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 11, "size wrong(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"boo hoo [3]"), "wrong output (%s)\n",buffer);
+
+    r = MsiRecordSetString(hrec, 0, "[1] [2] [[3]]");
+    r = MsiRecordSetString(hrec, 1, "boo");
+    r = MsiRecordSetString(hrec, 2, "hoo");
+    r = MsiRecordSetString(hrec, 3, "\\help");
+    ok( r == ERROR_SUCCESS, "set string failed\n");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(package, hrec, buffer, &sz);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+    ok( sz == 9, "size wrong(%li)\n",sz);
+    ok( 0 == strcmp(buffer,"boo hoo h"), "wrong output (%s)\n",buffer);
+
+    MsiCloseHandle(hrec);
+
+    r = MsiCloseHandle(package);
+    ok(r==ERROR_SUCCESS, "Unable to close package\n");
+
+    DeleteFile( filename );
+}
+
+START_TEST(format)
+{
+    test_createpackage();
+    test_formatrecord();
+    test_formatrecord_package();
+}
diff --git a/reactos/regtests/winetests/msi/install.c b/reactos/regtests/winetests/msi/install.c
new file mode 100644 (file)
index 0000000..880d01d
--- /dev/null
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2006 James Hawkins
+ *
+ * A test program for installing MSI products.
+ *
+ * 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 <stdio.h>
+
+#include <windows.h>
+#include <msiquery.h>
+#include <msidefs.h>
+#include <msi.h>
+#include <fci.h>
+
+#include "wine/test.h"
+#include "wine/windef.h"
+
+static const char *msifile = "winetest.msi";
+CHAR CURR_DIR[MAX_PATH];
+CHAR PROG_FILES_DIR[MAX_PATH];
+
+/* msi database data */
+
+static const CHAR admin_exec_seq_dat[] = "Action\tCondition\tSequence\n"
+                                         "s72\tS255\tI2\n"
+                                         "AdminExecuteSequence\tAction\n"
+                                         "CostFinalize\t\t1000\n"
+                                         "CostInitialize\t\t800\n"
+                                         "FileCost\t\t900\n"
+                                         "InstallAdminPackage\t\t3900\n"
+                                         "InstallFiles\t\t4000\n"
+                                         "InstallFinalize\t\t6600\n"
+                                         "InstallInitialize\t\t1500\n"
+                                         "InstallValidate\t\t1400";
+
+static const CHAR advt_exec_seq_dat[] = "Action\tCondition\tSequence\n"
+                                        "s72\tS255\tI2\n"
+                                        "AdvtExecuteSequence\tAction\n"
+                                        "CostFinalize\t\t1000\n"
+                                        "CostInitialize\t\t800\n"
+                                        "CreateShortcuts\t\t4500\n"
+                                        "InstallFinalize\t\t6600\n"
+                                        "InstallInitialize\t\t1500\n"
+                                        "InstallValidate\t\t1400\n"
+                                        "PublishComponents\t\t6200\n"
+                                        "PublishFeatures\t\t6300\n"
+                                        "PublishProduct\t\t6400\n"
+                                        "RegisterClassInfo\t\t4600\n"
+                                        "RegisterExtensionInfo\t\t4700\n"
+                                        "RegisterMIMEInfo\t\t4900\n"
+                                        "RegisterProgIdInfo\t\t4800";
+
+static const CHAR component_dat[] = "Component\tComponentId\tDirectory_\tAttributes\tCondition\tKeyPath\n"
+                                    "s72\tS38\ts72\ti2\tS255\tS72\n"
+                                    "Component\tComponent\n"
+                                    "Five\t{8CC92E9D-14B2-4CA4-B2AA-B11D02078087}\tNEWDIR\t2\t\tfive.txt\n"
+                                    "Four\t{FD37B4EA-7209-45C0-8917-535F35A2F080}\tCABOUTDIR\t2\t\tfour.txt\n"
+                                    "One\t{783B242E-E185-4A56-AF86-C09815EC053C}\tMSITESTDIR\t2\t\tone.txt\n"
+                                    "Three\t{010B6ADD-B27D-4EDD-9B3D-34C4F7D61684}\tCHANGEDDIR\t2\t\tthree.txt\n"
+                                    "Two\t{BF03D1A6-20DA-4A65-82F3-6CAC995915CE}\tFIRSTDIR\t2\t\ttwo.txt\n"
+                                    "dangler\t{6091DF25-EF96-45F1-B8E9-A9B1420C7A3C}\tTARGETDIR\t4\t\tregdata";
+
+static const CHAR directory_dat[] = "Directory\tDirectory_Parent\tDefaultDir\n"
+                                    "s72\tS72\tl255\n"
+                                    "Directory\tDirectory\n"
+                                    "CABOUTDIR\tMSITESTDIR\tcabout\n"
+                                    "CHANGEDDIR\tMSITESTDIR\tchanged:second\n"
+                                    "FIRSTDIR\tMSITESTDIR\tfirst\n"
+                                    "MSITESTDIR\tProgramFilesFolder\tmsitest\n"
+                                    "NEWDIR\tCABOUTDIR\tnew\n"
+                                    "ProgramFilesFolder\tTARGETDIR\t.\n"
+                                    "TARGETDIR\t\tSourceDir";
+
+static const CHAR feature_dat[] = "Feature\tFeature_Parent\tTitle\tDescription\tDisplay\tLevel\tDirectory_\tAttributes\n"
+                                  "s38\tS38\tL64\tL255\tI2\ti2\tS72\ti2\n"
+                                  "Feature\tFeature\n"
+                                  "Five\t\tFive\tThe Five Feature\t5\t3\tNEWDIR\t0\n"
+                                  "Four\t\tFour\tThe Four Feature\t4\t3\tCABOUTDIR\t0\n"
+                                  "One\t\tOne\tThe One Feature\t1\t3\tMSITESTDIR\t0\n"
+                                  "Three\t\tThree\tThe Three Feature\t3\t3\tCHANGEDDIR\t0\n"
+                                  "Two\t\tTwo\tThe Two Feature\t2\t3\tFIRSTDIR\t0";
+
+static const CHAR feature_comp_dat[] = "Feature_\tComponent_\n"
+                                       "s38\ts72\n"
+                                       "FeatureComponents\tFeature_\tComponent_\n"
+                                       "Five\tFive\n"
+                                       "Four\tFour\n"
+                                       "One\tOne\n"
+                                       "Three\tThree\n"
+                                       "Two\tTwo";
+
+static const CHAR file_dat[] = "File\tComponent_\tFileName\tFileSize\tVersion\tLanguage\tAttributes\tSequence\n"
+                               "s72\ts72\tl255\ti4\tS72\tS20\tI2\ti2\n"
+                               "File\tFile\n"
+                               "five.txt\tFive\tfive.txt\t1000\t\t\t16384\t5\n"
+                               "four.txt\tFour\tfour.txt\t1000\t\t\t16384\t4\n"
+                               "one.txt\tOne\tone.txt\t1000\t\t\t0\t1\n"
+                               "three.txt\tThree\tthree.txt\t1000\t\t\t0\t3\n"
+                               "two.txt\tTwo\ttwo.txt\t1000\t\t\t0\t2";
+
+static const CHAR install_exec_seq_dat[] = "Action\tCondition\tSequence\n"
+                                           "s72\tS255\tI2\n"
+                                           "InstallExecuteSequence\tAction\n"
+                                           "AllocateRegistrySpace\tNOT Installed\t1550\n"
+                                           "CostFinalize\t\t1000\n"
+                                           "CostInitialize\t\t800\n"
+                                           "FileCost\t\t900\n"
+                                           "InstallFiles\t\t4000\n"
+                                           "InstallFinalize\t\t6600\n"
+                                           "InstallInitialize\t\t1500\n"
+                                           "InstallValidate\t\t1400\n"
+                                           "LaunchConditions\t\t100\n"
+                                           "WriteRegistryValues\t\t5000";
+
+static const CHAR media_dat[] = "DiskId\tLastSequence\tDiskPrompt\tCabinet\tVolumeLabel\tSource\n"
+                                "i2\ti4\tL64\tS255\tS32\tS72\n"
+                                "Media\tDiskId\n"
+                                "1\t3\t\t\tDISK1\t\n"
+                                "2\t5\t\tmsitest.cab\tDISK2\t\n";
+
+static const CHAR property_dat[] = "Property\tValue\n"
+                                   "s72\tl0\n"
+                                   "Property\tProperty\n"
+                                   "DefaultUIFont\tDlgFont8\n"
+                                   "INSTALLLEVEL\t3\n"
+                                   "InstallMode\tTypical\n"
+                                   "Manufacturer\tWine\n"
+                                   "PIDTemplate\t12345<###-%%%%%%%>@@@@@\n"
+                                   "ProductCode\t{F1C3AF50-8B56-4A69-A00C-00773FE42F30}\n"
+                                   "ProductID\tnone\n"
+                                   "ProductLanguage\t1033\n"
+                                   "ProductName\tMSITEST\n"
+                                   "ProductVersion\t1.1.1\n"
+                                   "PROMPTROLLBACKCOST\tP\n"
+                                   "Setup\tSetup\n"
+                                   "UpgradeCode\t{CE067E8D-2E1A-4367-B734-4EB2BDAD6565}";
+
+static const CHAR registry_dat[] = "Registry\tRoot\tKey\tName\tValue\tComponent_\n"
+                                   "s72\ti2\tl255\tL255\tL0\ts72\n"
+                                   "Registry\tRegistry\n"
+                                   "Apples\t2\tSOFTWARE\\Wine\\msitest\tName\timaname\tOne\n"
+                                   "Oranges\t2\tSOFTWARE\\Wine\\msitest\tnumber\t#314\tTwo\n"
+                                   "regdata\t2\tSOFTWARE\\Wine\\msitest\tblah\tbad\tdangler";
+
+typedef struct _msi_table
+{
+    const CHAR *filename;
+    const CHAR *data;
+    int size;
+} msi_table;
+
+#define ADD_TABLE(x) {#x".idt", x##_dat, sizeof(x##_dat)}
+
+static const msi_table tables[] =
+{
+    ADD_TABLE(admin_exec_seq),
+    ADD_TABLE(advt_exec_seq),
+    ADD_TABLE(component),
+    ADD_TABLE(directory),
+    ADD_TABLE(feature),
+    ADD_TABLE(feature_comp),
+    ADD_TABLE(file),
+    ADD_TABLE(install_exec_seq),
+    ADD_TABLE(media),
+    ADD_TABLE(property),
+    ADD_TABLE(registry)
+};
+
+/* cabinet definitions */
+
+/* make the max size large so there is only one cab file */
+#define MEDIA_SIZE          999999999
+#define FOLDER_THRESHOLD    900000
+
+/* The following defintions were copied from dlls/cabinet/cabinet.h
+ * because they are undocumented in windows.
+ */
+
+/* EXTRACTdest flags */
+#define EXTRACT_FILLFILELIST  0x00000001
+#define EXTRACT_EXTRACTFILES  0x00000002
+
+struct ExtractFileList {
+    LPSTR  filename;
+    struct ExtractFileList *next;
+    BOOL   unknown;  /* always 1L */
+};
+
+/* the first parameter of the function extract */
+typedef struct {
+    long   result1;          /* 0x000 */
+    long   unknown1[3];      /* 0x004 */
+    struct ExtractFileList *filelist; /* 0x010 */
+    long   filecount;        /* 0x014 */
+    long   flags;            /* 0x018 */
+    char   directory[0x104]; /* 0x01c */
+    char   lastfile[0x20c];  /* 0x120 */
+} EXTRACTDEST;
+
+/* cabinet function pointers */
+HMODULE hCabinet;
+static HRESULT (WINAPI *pExtract)(EXTRACTDEST*, LPCSTR);
+
+/* the FCI callbacks */
+
+static void *mem_alloc(ULONG cb)
+{
+    return HeapAlloc(GetProcessHeap(), 0, cb);
+}
+
+static void mem_free(void *memory)
+{
+    HeapFree(GetProcessHeap(), 0, memory);
+}
+
+static BOOL get_next_cabinet(PCCAB pccab, ULONG  cbPrevCab, void *pv)
+{
+    return TRUE;
+}
+
+static long progress(UINT typeStatus, ULONG cb1, ULONG cb2, void *pv)
+{
+    return 0;
+}
+
+static int file_placed(PCCAB pccab, char *pszFile, long cbFile,
+                       BOOL fContinuation, void *pv)
+{
+    return 0;
+}
+
+static INT_PTR fci_open(char *pszFile, int oflag, int pmode, int *err, void *pv)
+{
+    HANDLE handle;
+    DWORD dwAccess = 0;
+    DWORD dwShareMode = 0;
+    DWORD dwCreateDisposition = OPEN_EXISTING;
+    
+    dwAccess = GENERIC_READ | GENERIC_WRITE;
+    dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+
+    if (GetFileAttributesA(pszFile) != INVALID_FILE_ATTRIBUTES)
+        dwCreateDisposition = OPEN_EXISTING;
+    else
+        dwCreateDisposition = CREATE_NEW;
+
+    handle = CreateFileA(pszFile, dwAccess, dwShareMode, NULL,
+                         dwCreateDisposition, 0, NULL);
+
+    ok(handle != INVALID_HANDLE_VALUE, "Failed to CreateFile %s\n", pszFile);
+
+    return (INT_PTR)handle;
+}
+
+static UINT fci_read(INT_PTR hf, void *memory, UINT cb, int *err, void *pv)
+{
+    HANDLE handle = (HANDLE)hf;
+    DWORD dwRead;
+    BOOL res;
+    
+    res = ReadFile(handle, memory, cb, &dwRead, NULL);
+    ok(res, "Failed to ReadFile\n");
+
+    return dwRead;
+}
+
+static UINT fci_write(INT_PTR hf, void *memory, UINT cb, int *err, void *pv)
+{
+    HANDLE handle = (HANDLE)hf;
+    DWORD dwWritten;
+    BOOL res;
+
+    res = WriteFile(handle, memory, cb, &dwWritten, NULL);
+    ok(res, "Failed to WriteFile\n");
+
+    return dwWritten;
+}
+
+static int fci_close(INT_PTR hf, int *err, void *pv)
+{
+    HANDLE handle = (HANDLE)hf;
+    ok(CloseHandle(handle), "Failed to CloseHandle\n");
+
+    return 0;
+}
+
+static long fci_seek(INT_PTR hf, long dist, int seektype, int *err, void *pv)
+{
+    HANDLE handle = (HANDLE)hf;
+    DWORD ret;
+    
+    ret = SetFilePointer(handle, dist, NULL, seektype);
+    ok(ret != INVALID_SET_FILE_POINTER, "Failed to SetFilePointer\n");
+
+    return ret;
+}
+
+static int fci_delete(char *pszFile, int *err, void *pv)
+{
+    BOOL ret = DeleteFileA(pszFile);
+    ok(ret, "Failed to DeleteFile %s\n", pszFile);
+
+    return 0;
+}
+
+static BOOL check_record(MSIHANDLE rec, UINT field, LPSTR val)
+{
+    CHAR buffer[0x20];
+    UINT r;
+    DWORD sz;
+
+    sz = sizeof buffer;
+    r = MsiRecordGetString(rec, field, buffer, &sz);
+    return (r == ERROR_SUCCESS ) && !strcmp(val, buffer);
+}
+
+static BOOL get_temp_file(char *pszTempName, int cbTempName, void *pv)
+{
+    LPSTR tempname;
+
+    tempname = HeapAlloc(GetProcessHeap(), 0, MAX_PATH);
+    GetTempFileNameA(".", "xx", 0, tempname);
+
+    if (tempname && (strlen(tempname) < (unsigned)cbTempName))
+    {
+        lstrcpyA(pszTempName, tempname);
+        HeapFree(GetProcessHeap(), 0, tempname);
+        return TRUE;
+    }
+
+    HeapFree(GetProcessHeap(), 0, tempname);
+
+    return FALSE;
+}
+
+static INT_PTR get_open_info(char *pszName, USHORT *pdate, USHORT *ptime,
+                             USHORT *pattribs, int *err, void *pv)
+{
+    BY_HANDLE_FILE_INFORMATION finfo;
+    FILETIME filetime;
+    HANDLE handle;
+    DWORD attrs;
+    BOOL res;
+
+    handle = CreateFile(pszName, GENERIC_READ, FILE_SHARE_READ, NULL,
+                        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+
+    ok(handle != INVALID_HANDLE_VALUE, "Failed to CreateFile %s\n", pszName);
+
+    res = GetFileInformationByHandle(handle, &finfo);
+    ok(res, "Expected GetFileInformationByHandle to succeed\n");
+   
+    FileTimeToLocalFileTime(&finfo.ftLastWriteTime, &filetime);
+    FileTimeToDosDateTime(&filetime, pdate, ptime);
+
+    attrs = GetFileAttributes(pszName);
+    ok(attrs != INVALID_FILE_ATTRIBUTES, "Failed to GetFileAttributes\n");
+
+    return (INT_PTR)handle;
+}
+
+static void add_file(HFCI hfci, char *file)
+{
+    char path[MAX_PATH];
+    BOOL res;
+
+    lstrcpyA(path, CURR_DIR);
+    lstrcatA(path, "\\");
+    lstrcatA(path, file);
+
+    res = FCIAddFile(hfci, path, file, FALSE, get_next_cabinet, progress,
+                     get_open_info, tcompTYPE_MSZIP);
+    ok(res, "Expected FCIAddFile to succeed\n");
+}
+
+static void set_cab_parameters(PCCAB pCabParams, const CHAR *name)
+{
+    ZeroMemory(pCabParams, sizeof(CCAB));
+
+    pCabParams->cb = MEDIA_SIZE;
+    pCabParams->cbFolderThresh = FOLDER_THRESHOLD;
+    pCabParams->setID = 0xbeef;
+    lstrcpyA(pCabParams->szCabPath, CURR_DIR);
+    lstrcatA(pCabParams->szCabPath, "\\");
+    lstrcpyA(pCabParams->szCab, name);
+}
+
+static void create_cab_file(const CHAR *name)
+{
+    CCAB cabParams;
+    HFCI hfci;
+    ERF erf;
+    BOOL res;
+
+    set_cab_parameters(&cabParams, name);
+
+    hfci = FCICreate(&erf, file_placed, mem_alloc, mem_free, fci_open,
+                      fci_read, fci_write, fci_close, fci_seek, fci_delete,
+                      get_temp_file, &cabParams, NULL);
+
+    ok(hfci != NULL, "Failed to create an FCI context\n");
+
+    add_file(hfci, "four.txt");
+    add_file(hfci, "five.txt");
+
+    res = FCIFlushCabinet(hfci, FALSE, get_next_cabinet, progress);
+    ok(res, "Failed to flush the cabinet\n");
+
+    res = FCIDestroy(hfci);
+    ok(res, "Failed to destroy the cabinet\n");
+}
+
+static BOOL init_function_pointers(void)
+{
+    hCabinet = LoadLibraryA("cabinet.dll");
+    if (!hCabinet)
+        return FALSE;
+
+    pExtract = (void *)GetProcAddress(hCabinet, "Extract");
+    if (!pExtract)
+        return FALSE;
+
+    return TRUE;
+}
+
+static BOOL get_program_files_dir(LPSTR buf)
+{
+    HKEY hkey;
+    CHAR temp[MAX_PATH];
+    DWORD type = REG_EXPAND_SZ, size;
+
+    if (RegOpenKey(HKEY_LOCAL_MACHINE,
+                   "Software\\Microsoft\\Windows\\CurrentVersion", &hkey))
+        return FALSE;
+
+    size = MAX_PATH;
+    if (RegQueryValueEx(hkey, "ProgramFilesPath", 0, &type, (LPBYTE)temp, &size))
+        return FALSE;
+
+    ExpandEnvironmentStrings(temp, buf, MAX_PATH);
+
+    RegCloseKey(hkey);
+    return TRUE;
+}
+
+static void create_file(const CHAR *name)
+{
+    HANDLE file;
+    DWORD written;
+
+    file = CreateFileA(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
+    ok(file != INVALID_HANDLE_VALUE, "Failure to open file %s\n", name);
+    WriteFile(file, name, strlen(name), &written, NULL);
+    WriteFile(file, "\n", strlen("\n"), &written, NULL);
+    CloseHandle(file);
+}
+
+static void create_test_files(void)
+{
+    int len;
+
+    GetCurrentDirectoryA(MAX_PATH, CURR_DIR);
+    len = lstrlenA(CURR_DIR);
+
+    if(len && (CURR_DIR[len-1] == '\\'))
+        CURR_DIR[len - 1] = 0;
+
+    get_program_files_dir(PROG_FILES_DIR);
+
+    CreateDirectoryA("msitest", NULL);
+    create_file("msitest\\one.txt");
+    CreateDirectoryA("msitest\\first", NULL);
+    create_file("msitest\\first\\two.txt");
+    CreateDirectoryA("msitest\\second", NULL);
+    create_file("msitest\\second\\three.txt");
+
+    create_file("four.txt");
+    create_file("five.txt");
+    create_cab_file("msitest.cab");
+
+    DeleteFileA("four.txt");
+    DeleteFileA("five.txt");
+}
+
+static BOOL delete_pf(const CHAR *rel_path, BOOL is_file)
+{
+    CHAR path[MAX_PATH];
+
+    lstrcpyA(path, PROG_FILES_DIR);
+    lstrcatA(path, "\\");
+    lstrcatA(path, rel_path);
+
+    if (is_file)
+        return DeleteFileA(path);
+    else
+        return RemoveDirectoryA(path);
+}
+
+static void delete_test_files(void)
+{
+    DeleteFileA("msitest.msi");
+    DeleteFileA("msitest.cab");
+    DeleteFileA("msitest\\second\\three.txt");
+    DeleteFileA("msitest\\first\\two.txt");
+    DeleteFileA("msitest\\one.txt");
+    RemoveDirectoryA("msitest\\second");
+    RemoveDirectoryA("msitest\\first");
+    RemoveDirectoryA("msitest");
+}
+
+static void write_file(const CHAR *filename, const char *data, int data_size)
+{
+    DWORD size;
+
+    HANDLE hf = CreateFile(filename, GENERIC_WRITE, 0, NULL,
+                           CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+
+    WriteFile(hf, data, data_size, &size, NULL);
+    CloseHandle(hf);
+}
+
+static void write_msi_summary_info(MSIHANDLE db)
+{
+    MSIHANDLE summary;
+    UINT r;
+
+    r = MsiGetSummaryInformationA(db, NULL, 4, &summary);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    r = MsiSummaryInfoSetPropertyA(summary, PID_TEMPLATE, VT_LPSTR, 0, NULL, ";1033");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    r = MsiSummaryInfoSetPropertyA(summary, PID_REVNUMBER, VT_LPSTR, 0, NULL,
+                                   "{004757CA-5092-49c2-AD20-28E1CE0DF5F2}");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    r = MsiSummaryInfoSetPropertyA(summary, PID_PAGECOUNT, VT_I4, 100, NULL, NULL);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    r = MsiSummaryInfoSetPropertyA(summary, PID_WORDCOUNT, VT_I4, 0, NULL, NULL);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    /* write the summary changes back to the stream */
+    r = MsiSummaryInfoPersist(summary);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    MsiCloseHandle(summary);
+}
+
+static void create_database(const CHAR *name, const msi_table *tables, int num_tables)
+{
+    MSIHANDLE db;
+    UINT r;
+    int j;
+
+    r = MsiOpenDatabaseA(name, MSIDBOPEN_CREATE, &db);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    /* import the tables into the database */
+    for (j = 0; j < num_tables; j++)
+    {
+        const msi_table *table = &tables[j];
+
+        write_file(table->filename, table->data, (table->size - 1) * sizeof(char));
+
+        r = MsiDatabaseImportA(db, CURR_DIR, table->filename);
+        todo_wine
+        {
+            ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+        }
+
+        DeleteFileA(table->filename);
+    }
+
+    write_msi_summary_info(db);
+
+    r = MsiDatabaseCommit(db);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    MsiCloseHandle(db);
+}
+
+static void test_MsiInstallProduct(void)
+{
+    UINT r;
+    CHAR path[MAX_PATH];
+    LONG res;
+    HKEY hkey;
+    DWORD num, size, type;
+
+    r = MsiInstallProductA(msifile, NULL);
+    todo_wine
+    {
+        ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+    }
+
+    todo_wine
+    {
+        ok(delete_pf("msitest\\cabout\\new\\five.txt", TRUE), "File not installed\n");
+        ok(delete_pf("msitest\\cabout\\new", FALSE), "File not installed\n");
+        ok(delete_pf("msitest\\cabout\\four.txt", TRUE), "File not installed\n");
+        ok(delete_pf("msitest\\cabout", FALSE), "File not installed\n");
+        ok(delete_pf("msitest\\changed\\three.txt", TRUE), "File not installed\n");
+        ok(delete_pf("msitest\\changed", FALSE), "File not installed\n");
+        ok(delete_pf("msitest\\first\\two.txt", TRUE), "File not installed\n");
+        ok(delete_pf("msitest\\first", FALSE), "File not installed\n");
+        ok(delete_pf("msitest\\one.txt", TRUE), "File not installed\n");
+        ok(delete_pf("msitest", FALSE), "File not installed\n");
+    }
+
+    res = RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\Wine\\msitest", &hkey);
+    todo_wine
+    {
+        ok(res == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %ld\n", res);
+    }
+
+    size = MAX_PATH;
+    type = REG_SZ;
+    res = RegQueryValueExA(hkey, "Name", NULL, &type, (LPBYTE)path, &size);
+    todo_wine
+    {
+        ok(res == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %ld\n", res);
+        ok(!lstrcmpA(path, "imaname"), "Expected imaname, got %s\n", path);
+    }
+
+    size = MAX_PATH;
+    type = REG_SZ;
+    res = RegQueryValueExA(hkey, "blah", NULL, &type, (LPBYTE)path, &size);
+    todo_wine
+    {
+        ok(res == ERROR_FILE_NOT_FOUND, "Expected ERROR_FILE_NOT_FOUND, got %ld\n", res);
+    }
+
+    size = sizeof(num);
+    type = REG_DWORD;
+    res = RegQueryValueExA(hkey, "number", NULL, &type, (LPBYTE)&num, &size);
+    todo_wine
+    {
+        ok(res == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %ld\n", res);
+        ok(num == 314, "Expected 314, got %ld\n", num);
+    }
+
+    RegDeleteKeyA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Wine\\msitest");
+}
+
+static void test_MsiSetComponentState(void)
+{
+    MSIHANDLE package;
+    char path[MAX_PATH];
+    UINT r;
+
+    CoInitialize(NULL);
+
+    lstrcpy(path, CURR_DIR);
+    lstrcat(path, "\\");
+    lstrcat(path, msifile);
+
+    r = MsiOpenPackage(path, &package);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    r = MsiDoAction(package, "CostInitialize");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    r = MsiDoAction(package, "FileCost");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    r = MsiDoAction(package, "CostFinalize");
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    r = MsiSetComponentState(package, "dangler", INSTALLSTATE_SOURCE);
+    todo_wine
+    {
+        ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+    }
+
+    MsiCloseHandle(package);
+    CoUninitialize();
+}
+
+static void test_packagecoltypes(void)
+{
+    MSIHANDLE hdb, view, rec;
+    char path[MAX_PATH];
+    LPSTR query;
+    UINT r, count;
+
+    CoInitialize(NULL);
+
+    lstrcpy(path, CURR_DIR);
+    lstrcat(path, "\\");
+    lstrcat(path, msifile);
+
+    r = MsiOpenDatabase(path, MSIDBOPEN_READONLY, &hdb);
+    ok(r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r);
+
+    query = "SELECT * FROM `Media`";
+    r = MsiDatabaseOpenView( hdb, query, &view );
+    todo_wine
+    {
+        ok(r == ERROR_SUCCESS, "MsiDatabaseOpenView failed\n");
+    }
+
+    r = MsiViewGetColumnInfo( view, MSICOLINFO_NAMES, &rec );
+    count = MsiRecordGetFieldCount( rec );
+    todo_wine
+    {
+        ok(r == ERROR_SUCCESS, "MsiViewGetColumnInfo failed\n");
+        ok(count == 6, "Expected 6, got %d\n", count);
+        ok(check_record(rec, 1, "DiskId"), "wrong column label\n");
+        ok(check_record(rec, 2, "LastSequence"), "wrong column label\n");
+        ok(check_record(rec, 3, "DiskPrompt"), "wrong column label\n");
+        ok(check_record(rec, 4, "Cabinet"), "wrong column label\n");
+        ok(check_record(rec, 5, "VolumeLabel"), "wrong column label\n");
+        ok(check_record(rec, 6, "Source"), "wrong column label\n");
+    }
+
+    r = MsiViewGetColumnInfo( view, MSICOLINFO_TYPES, &rec );
+    count = MsiRecordGetFieldCount( rec );
+    todo_wine
+    {
+        ok(r == ERROR_SUCCESS, "MsiViewGetColumnInfo failed\n");
+        ok(count == 6, "Expected 6, got %d\n", count);
+        ok(check_record(rec, 1, "i2"), "wrong column label\n");
+        ok(check_record(rec, 2, "i4"), "wrong column label\n");
+        ok(check_record(rec, 3, "L64"), "wrong column label\n");
+        ok(check_record(rec, 4, "S255"), "wrong column label\n");
+        ok(check_record(rec, 5, "S32"), "wrong column label\n");
+        ok(check_record(rec, 6, "S72"), "wrong column label\n");
+    }
+
+    MsiCloseHandle(hdb);
+    DeleteFile(msifile);
+}
+
+START_TEST(install)
+{
+    if (!init_function_pointers())
+        return;
+
+    create_test_files();
+    create_database(msifile, tables, sizeof(tables) / sizeof(msi_table));
+    
+    test_MsiInstallProduct();
+    test_MsiSetComponentState();
+    test_packagecoltypes();
+    
+    delete_test_files();
+}
diff --git a/reactos/regtests/winetests/msi/msi.c b/reactos/regtests/winetests/msi/msi.c
new file mode 100644 (file)
index 0000000..bc92eeb
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * tests for Microsoft Installer functionality
+ *
+ * Copyright 2005 Mike McCormack for CodeWeavers
+ *
+ * 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 <stdio.h>
+#include <windows.h>
+#include <msi.h>
+#include <msiquery.h>
+
+#include "wine/test.h"
+#include "wine/windef.h"
+
+typedef struct test_MSIFILEHASHINFO {
+    ULONG dwFileHashInfoSize;
+    ULONG dwData[4];
+} test_MSIFILEHASHINFO, *test_PMSIFILEHASHINFO;
+
+typedef INSTALLSTATE (WINAPI *fnMsiUseFeatureExA)(LPCSTR, LPCSTR ,DWORD, DWORD );
+fnMsiUseFeatureExA pMsiUseFeatureExA;
+typedef UINT (WINAPI *fnMsiOpenPackageExA)(LPCSTR, DWORD, MSIHANDLE*);
+fnMsiOpenPackageExA pMsiOpenPackageExA;
+typedef UINT (WINAPI *fnMsiOpenPackageExW)(LPCWSTR, DWORD, MSIHANDLE*);
+fnMsiOpenPackageExW pMsiOpenPackageExW;
+typedef INSTALLSTATE (WINAPI *fnMsiGetComponentPathA)(LPCSTR, LPCSTR, LPSTR, DWORD*);
+fnMsiGetComponentPathA pMsiGetComponentPathA;
+typedef UINT (WINAPI *fnMsiGetFileHashA)(LPCSTR, DWORD, test_PMSIFILEHASHINFO);
+fnMsiGetFileHashA pMsiGetFileHashA;
+
+static void test_usefeature(void)
+{
+    UINT r;
+
+    if (!pMsiUseFeatureExA)
+        return;
+
+    r = MsiQueryFeatureState(NULL,NULL);
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return val\n");
+
+    r = MsiQueryFeatureState("{9085040-6000-11d3-8cfe-0150048383c9}" ,NULL);
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return val\n");
+
+    r = pMsiUseFeatureExA(NULL,NULL,0,0);
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return val\n");
+
+    r = pMsiUseFeatureExA(NULL, "WORDVIEWFiles", -2, 1 );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return val\n");
+
+    r = pMsiUseFeatureExA("{90850409-6000-11d3-8cfe-0150048383c9}", 
+                         NULL, -2, 0 );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return val\n");
+
+    r = pMsiUseFeatureExA("{9085040-6000-11d3-8cfe-0150048383c9}", 
+                         "WORDVIEWFiles", -2, 0 );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return val\n");
+
+    r = pMsiUseFeatureExA("{0085040-6000-11d3-8cfe-0150048383c9}", 
+                         "WORDVIEWFiles", -2, 0 );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return val\n");
+
+    r = pMsiUseFeatureExA("{90850409-6000-11d3-8cfe-0150048383c9}", 
+                         "WORDVIEWFiles", -2, 1 );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return val\n");
+}
+
+static void test_null(void)
+{
+    MSIHANDLE hpkg;
+    UINT r;
+
+    r = pMsiOpenPackageExW(NULL, 0, &hpkg);
+    ok( r == ERROR_INVALID_PARAMETER,"wrong error\n");
+
+    r = MsiQueryProductStateW(NULL);
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return\n");
+
+    r = MsiEnumFeaturesW(NULL,0,NULL,NULL);
+    ok( r == ERROR_INVALID_PARAMETER,"wrong error\n");
+
+    r = MsiConfigureFeatureW(NULL, NULL, 0);
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error\n");
+
+    r = MsiConfigureFeatureA("{00000000-0000-0000-0000-000000000000}", NULL, 0);
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error\n");
+
+    r = MsiConfigureFeatureA("{00000000-0000-0000-0000-000000000000}", "foo", 0);
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error %d\n", r);
+
+    r = MsiConfigureFeatureA("{00000000-0000-0000-0000-000000000000}", "foo", INSTALLSTATE_DEFAULT);
+    ok( r == ERROR_UNKNOWN_PRODUCT, "wrong error %d\n", r);
+}
+
+static void test_getcomponentpath(void)
+{
+    INSTALLSTATE r;
+    char buffer[0x100];
+    DWORD sz;
+
+    if(!pMsiGetComponentPathA)
+        return;
+
+    r = pMsiGetComponentPathA( NULL, NULL, NULL, NULL );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return value\n");
+
+    r = pMsiGetComponentPathA( "bogus", "bogus", NULL, NULL );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return value\n");
+
+    r = pMsiGetComponentPathA( "bogus", "{00000000-0000-0000-000000000000}", NULL, NULL );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return value\n");
+
+    sz = sizeof buffer;
+    buffer[0]=0;
+    r = pMsiGetComponentPathA( "bogus", "{00000000-0000-0000-000000000000}", buffer, &sz );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return value\n");
+
+    r = pMsiGetComponentPathA( "{00000000-78E1-11D2-B60F-006097C998E7}",
+        "{00000000-0000-0000-0000-000000000000}", buffer, &sz );
+    ok( r == INSTALLSTATE_UNKNOWN, "wrong return value\n");
+
+    r = pMsiGetComponentPathA( "{00000409-78E1-11D2-B60F-006097C998E7}",
+        "{00000000-0000-0000-0000-00000000}", buffer, &sz );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return value\n");
+
+    r = pMsiGetComponentPathA( "{00000409-78E1-11D2-B60F-006097C998E7}",
+        "{029E403D-A86A-1D11-5B5B0006799C897E}", buffer, &sz );
+    ok( r == INSTALLSTATE_INVALIDARG, "wrong return value\n");
+
+    r = pMsiGetComponentPathA( "{00000000-78E1-11D2-B60F-006097C9987e}",
+                            "{00000000-A68A-11d1-5B5B-0006799C897E}", buffer, &sz );
+    ok( r == INSTALLSTATE_UNKNOWN, "wrong return value\n");
+}
+
+static void test_filehash(void)
+{
+    const char name[] = "msitest.bin";
+    const char data[] = {'a','b','c'};
+    HANDLE handle;
+    UINT r;
+    test_MSIFILEHASHINFO hash;
+    DWORD count = 0;
+
+    if (!pMsiGetFileHashA)
+        return;
+
+    DeleteFile(name);
+
+    memset(&hash, 0, sizeof hash);
+    r = pMsiGetFileHashA(name, 0, &hash);
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error %d\n", r);
+
+    r = pMsiGetFileHashA(name, 0, NULL);
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error %d\n", r);
+
+    memset(&hash, 0, sizeof hash);
+    hash.dwFileHashInfoSize = sizeof hash;
+    r = pMsiGetFileHashA(name, 0, &hash);
+    ok( r == ERROR_FILE_NOT_FOUND, "wrong error %d\n", r);
+
+    handle = CreateFile(name, GENERIC_READ|GENERIC_WRITE, 0, NULL, 
+                CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL);
+    ok(handle != INVALID_HANDLE_VALUE, "failed to create file\n");
+
+    WriteFile(handle, data, sizeof data, &count, NULL);
+    CloseHandle(handle);
+
+    memset(&hash, 0, sizeof hash);
+    r = pMsiGetFileHashA(name, 0, &hash);
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error %d\n", r);
+
+    memset(&hash, 0, sizeof hash);
+    hash.dwFileHashInfoSize = sizeof hash;
+    r = pMsiGetFileHashA(name, 1, &hash);
+    ok( r == ERROR_INVALID_PARAMETER, "wrong error %d\n", r);
+
+    r = pMsiGetFileHashA(name, 0, &hash);
+    ok( r == ERROR_SUCCESS, "wrong error %d\n", r);
+
+    ok(hash.dwFileHashInfoSize == sizeof hash, "hash size changed\n");
+    ok(hash.dwData[0] == 0x98500190 &&
+       hash.dwData[1] == 0xb04fd23c &&
+       hash.dwData[2] == 0x7d3f96d6 &&
+       hash.dwData[3] == 0x727fe128, "hash of abc incorrect\n");
+
+    DeleteFile(name);
+}
+
+START_TEST(msi)
+{
+    HMODULE hmod = GetModuleHandle("msi.dll");
+    pMsiUseFeatureExA = (fnMsiUseFeatureExA) 
+        GetProcAddress(hmod, "MsiUseFeatureExA");
+    pMsiOpenPackageExA = (fnMsiOpenPackageExA) 
+        GetProcAddress(hmod, "MsiOpenPackageExA");
+    pMsiOpenPackageExW = (fnMsiOpenPackageExW) 
+        GetProcAddress(hmod, "MsiOpenPackageExW");
+    pMsiGetComponentPathA = (fnMsiGetComponentPathA)
+        GetProcAddress(hmod, "MsiGetComponentPathA" );
+    pMsiGetFileHashA = (fnMsiGetFileHashA)
+        GetProcAddress(hmod, "MsiGetFileHashA" );
+
+    test_usefeature();
+    test_null();
+    test_getcomponentpath();
+    test_filehash();
+}
diff --git a/reactos/regtests/winetests/msi/msi.rbuild b/reactos/regtests/winetests/msi/msi.rbuild
new file mode 100644 (file)
index 0000000..f073b8e
--- /dev/null
@@ -0,0 +1,18 @@
+<module name="msi_winetest" type="win32cui" installbase="bin" installname="msi_winetest.exe" allowwarnings="true">\r
+    <include base="msi_winetest">.</include>\r
+    <define name="__USE_W32API" />\r
+    <library>cabinet</library>\r
+    <library>msi</library>\r
+    <library>ole32</library>\r
+    <library>advapi32</library>\r
+    <library>kernel32</library>\r
+    <library>ntdll</library>\r
+    <file>db.c</file>\r
+    <file>format.c</file>\r
+    <file>install.c</file>\r
+    <file>msi.c</file>\r
+    <file>package.c</file>\r
+    <file>record.c</file>\r
+    <file>suminfo.c</file>\r
+    <file>testlist.c</file>\r
+</module>\r
diff --git a/reactos/regtests/winetests/msi/package.c b/reactos/regtests/winetests/msi/package.c
new file mode 100644 (file)
index 0000000..a66bfdc
--- /dev/null
@@ -0,0 +1,1769 @@
+/*
+ * tests for Microsoft Installer functionality
+ *
+ * Copyright 2005 Mike McCormack for CodeWeavers
+ * Copyright 2005 Aric Stewart for CodeWeavers
+ *
+ * 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
+ */
+
+#define COBJMACROS
+
+#include <stdio.h>
+#include <windows.h>
+#include <msi.h>
+#include <msiquery.h>
+
+#include "wine/test.h"
+#include "wine/windef.h"
+
+static const char msifile[] = "winetest.msi";
+
+static UINT run_query( MSIHANDLE hdb, const char *query )
+{
+    MSIHANDLE hview = 0;
+    UINT r;
+
+    r = MsiDatabaseOpenView(hdb, query, &hview);
+    if( r != ERROR_SUCCESS )
+        return r;
+
+    r = MsiViewExecute(hview, 0);
+    if( r == ERROR_SUCCESS )
+        r = MsiViewClose(hview);
+    MsiCloseHandle(hview);
+    return r;
+}
+
+static UINT create_component_table( MSIHANDLE hdb )
+{
+    return run_query( hdb,
+            "CREATE TABLE `Component` ( "
+            "`Component` CHAR(72) NOT NULL, "
+            "`ComponentId` CHAR(38), "
+            "`Directory_` CHAR(72) NOT NULL, "
+            "`Attributes` SHORT NOT NULL, "
+            "`Condition` CHAR(255), "
+            "`KeyPath` CHAR(72) "
+            "PRIMARY KEY `Component`)" );
+}
+
+static UINT create_feature_table( MSIHANDLE hdb )
+{
+    return run_query( hdb,
+            "CREATE TABLE `Feature` ( "
+            "`Feature` CHAR(38) NOT NULL, "
+            "`Feature_Parent` CHAR(38), "
+            "`Title` CHAR(64), "
+            "`Description` CHAR(255), "
+            "`Display` SHORT NOT NULL, "
+            "`Level` SHORT NOT NULL, "
+            "`Directory_` CHAR(72), "
+            "`Attributes` SHORT NOT NULL "
+            "PRIMARY KEY `Feature`)" );
+}
+
+static UINT create_feature_components_table( MSIHANDLE hdb )
+{
+    return run_query( hdb,
+            "CREATE TABLE `FeatureComponents` ( "
+            "`Feature_` CHAR(38) NOT NULL, "
+            "`Component_` CHAR(72) NOT NULL "
+            "PRIMARY KEY `Feature_`, `Component_` )" );
+}
+
+static UINT create_file_table( MSIHANDLE hdb )
+{
+    return run_query( hdb,
+            "CREATE TABLE `File` ("
+            "`File` CHAR(72) NOT NULL, "
+            "`Component_` CHAR(72) NOT NULL, "
+            "`FileName` CHAR(255) NOT NULL, "
+            "`FileSize` LONG NOT NULL, "
+            "`Version` CHAR(72), "
+            "`Language` CHAR(20), "
+            "`Attributes` SHORT, "
+            "`Sequence` SHORT NOT NULL "
+            "PRIMARY KEY `File`)" );
+}
+
+static UINT add_component_entry( MSIHANDLE hdb, char *values )
+{
+    char insert[] = "INSERT INTO `Component`  "
+            "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `Condition`, `KeyPath`) "
+            "VALUES( %s )";
+    char *query;
+    UINT sz, r;
+
+    sz = strlen(values) + sizeof insert;
+    query = HeapAlloc(GetProcessHeap(),0,sz);
+    sprintf(query,insert,values);
+    r = run_query( hdb, query );
+    HeapFree(GetProcessHeap(), 0, query);
+    return r;
+}
+
+static UINT add_feature_entry( MSIHANDLE hdb, char *values )
+{
+    char insert[] = "INSERT INTO `Feature` (`Feature`, `Feature_Parent`, "
+                    "`Display`, `Level`, `Attributes`) VALUES( %s )";
+    char *query;
+    UINT sz, r;
+
+    sz = strlen(values) + sizeof insert;
+    query = HeapAlloc(GetProcessHeap(),0,sz);
+    sprintf(query,insert,values);
+    r = run_query( hdb, query );
+    HeapFree(GetProcessHeap(), 0, query);
+    return r;
+}
+
+static UINT add_feature_components_entry( MSIHANDLE hdb, char *values )
+{
+    char insert[] = "INSERT INTO `FeatureComponents` "
+            "(`Feature_`, `Component_`) "
+            "VALUES( %s )";
+    char *query;
+    UINT sz, r;
+
+    sz = strlen(values) + sizeof insert;
+    query = HeapAlloc(GetProcessHeap(),0,sz);
+    sprintf(query,insert,values);
+    r = run_query( hdb, query );
+    HeapFree(GetProcessHeap(), 0, query);
+    return r;
+}
+
+static UINT add_file_entry( MSIHANDLE hdb, char *values )
+{
+    char insert[] = "INSERT INTO `File` "
+            "(`File`, `Component_`, `FileName`, `FileSize`, `Version`, `Language`, `Attributes`, `Sequence`) "
+            "VALUES( %s )";
+    char *query;
+    UINT sz, r;
+
+    sz = strlen(values) + sizeof insert;
+    query = HeapAlloc(GetProcessHeap(),0,sz);
+    sprintf(query,insert,values);
+    r = run_query( hdb, query );
+    HeapFree(GetProcessHeap(), 0, query);
+    return r;
+}
+
+static UINT set_summary_info(MSIHANDLE hdb)
+{
+    UINT res;
+    MSIHANDLE suminfo;
+
+    /* build summmary info */
+    res = MsiGetSummaryInformation(hdb, NULL, 7, &suminfo);
+    ok( res == ERROR_SUCCESS , "Failed to open summaryinfo\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,2, VT_LPSTR, 0,NULL,
+                        "Installation Database");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,3, VT_LPSTR, 0,NULL,
+                        "Installation Database");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,4, VT_LPSTR, 0,NULL,
+                        "Wine Hackers");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,7, VT_LPSTR, 0,NULL,
+                    ";1033");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo,9, VT_LPSTR, 0,NULL,
+                    "{913B8D18-FBB6-4CAC-A239-C74C11E3FA74}");
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo, 14, VT_I4, 100, NULL, NULL);
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoSetProperty(suminfo, 15, VT_I4, 0, NULL, NULL);
+    ok( res == ERROR_SUCCESS , "Failed to set summary info\n" );
+
+    res = MsiSummaryInfoPersist(suminfo);
+    ok( res == ERROR_SUCCESS , "Failed to make summary info persist\n" );
+
+    res = MsiCloseHandle( suminfo);
+    ok( res == ERROR_SUCCESS , "Failed to close suminfo\n" );
+
+    return res;
+}
+
+
+MSIHANDLE create_package_db(void)
+{
+    MSIHANDLE hdb = 0;
+    UINT res;
+
+    DeleteFile(msifile);
+
+    /* create an empty database */
+    res = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb );
+    ok( res == ERROR_SUCCESS , "Failed to create database\n" );
+    if( res != ERROR_SUCCESS )
+        return hdb;
+
+    res = MsiDatabaseCommit( hdb );
+    ok( res == ERROR_SUCCESS , "Failed to commit database\n" );
+
+    res = set_summary_info(hdb);
+
+    res = run_query( hdb,
+            "CREATE TABLE `Directory` ( "
+            "`Directory` CHAR(255) NOT NULL, "
+            "`Directory_Parent` CHAR(255), "
+            "`DefaultDir` CHAR(255) NOT NULL "
+            "PRIMARY KEY `Directory`)" );
+    ok( res == ERROR_SUCCESS , "Failed to create directory table\n" );
+
+    return hdb;
+}
+
+MSIHANDLE package_from_db(MSIHANDLE hdb)
+{
+    UINT res;
+    CHAR szPackage[10];
+    MSIHANDLE hPackage;
+
+    sprintf(szPackage,"#%li",hdb);
+    res = MsiOpenPackage(szPackage,&hPackage);
+    ok( res == ERROR_SUCCESS , "Failed to open package\n" );
+
+    res = MsiCloseHandle(hdb);
+    ok( res == ERROR_SUCCESS , "Failed to close db handle\n" );
+
+    return hPackage;
+}
+
+static void test_createpackage(void)
+{
+    MSIHANDLE hPackage = 0;
+    UINT res;
+
+    hPackage = package_from_db(create_package_db());
+    ok( hPackage != 0, " Failed to create package\n");
+
+    res = MsiCloseHandle( hPackage);
+    ok( res == ERROR_SUCCESS , "Failed to close package\n" );
+    DeleteFile(msifile);
+}
+
+static void test_getsourcepath_bad( void )
+{
+    static const char str[] = { 0 };
+    char buffer[0x80];
+    DWORD sz;
+    UINT r;
+
+    r = MsiGetSourcePath( -1, NULL, NULL, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "return value wrong\n");
+
+    sz = 0;
+    r = MsiGetSourcePath( -1, NULL, buffer, &sz );
+    ok( r == ERROR_INVALID_PARAMETER, "return value wrong\n");
+
+    sz = 0;
+    r = MsiGetSourcePath( -1, str, NULL, &sz );
+    ok( r == ERROR_INVALID_HANDLE, "return value wrong\n");
+
+    sz = 0;
+    r = MsiGetSourcePath( -1, str, NULL, NULL );
+    ok( r == ERROR_INVALID_HANDLE, "return value wrong\n");
+
+    sz = 0;
+    r = MsiGetSourcePath( -1, str, buffer, &sz );
+    ok( r == ERROR_INVALID_HANDLE, "return value wrong\n");
+}
+
+static UINT add_directory_entry( MSIHANDLE hdb, char *values )
+{
+    char insert[] = "INSERT INTO `Directory` (`Directory`,`Directory_Parent`,`DefaultDir`) VALUES( %s )";
+    char *query;
+    UINT sz, r;
+
+    sz = strlen(values) + sizeof insert;
+    query = HeapAlloc(GetProcessHeap(),0,sz);
+    sprintf(query,insert,values);
+    r = run_query( hdb, query );
+    HeapFree(GetProcessHeap(), 0, query);
+    return r;
+}
+
+static void test_getsourcepath( void )
+{
+    static const char str[] = { 0 };
+    char buffer[0x80];
+    DWORD sz;
+    UINT r;
+    MSIHANDLE hpkg, hdb;
+
+    hpkg = package_from_db(create_package_db());
+    ok( hpkg, "failed to create package\n");
+
+    sz = 0;
+    buffer[0] = 'x';
+    r = MsiGetSourcePath( hpkg, str, buffer, &sz );
+    ok( r == ERROR_DIRECTORY, "return value wrong\n");
+    ok( buffer[0] == 'x', "buffer modified\n");
+
+    sz = 1;
+    buffer[0] = 'x';
+    r = MsiGetSourcePath( hpkg, str, buffer, &sz );
+    ok( r == ERROR_DIRECTORY, "return value wrong\n");
+    ok( buffer[0] == 'x', "buffer modified\n");
+
+    MsiCloseHandle( hpkg );
+
+
+    /* another test but try create a directory this time */
+    hdb = create_package_db();
+    ok( hdb, "failed to create database\n");
+    
+    r = add_directory_entry( hdb, "'TARGETDIR', '', 'SourceDir'");
+    ok( r == S_OK, "failed\n");
+
+    hpkg = package_from_db(hdb);
+    ok( hpkg, "failed to create package\n");
+
+    sz = sizeof buffer -1;
+    strcpy(buffer,"x bad");
+    r = MsiGetSourcePath( hpkg, "TARGETDIR", buffer, &sz );
+    ok( r == ERROR_DIRECTORY, "return value wrong\n");
+
+    r = MsiDoAction( hpkg, "CostInitialize");
+    ok( r == ERROR_SUCCESS, "cost init failed\n");
+    r = MsiDoAction( hpkg, "CostFinalize");
+    ok( r == ERROR_SUCCESS, "cost finalize failed\n");
+
+    todo_wine {
+    sz = sizeof buffer -1;
+    buffer[0] = 'x';
+    r = MsiGetSourcePath( hpkg, "TARGETDIR", buffer, &sz );
+    ok( r == ERROR_SUCCESS, "return value wrong\n");
+    ok( sz == strlen(buffer), "returned length wrong\n");
+
+    sz = 0;
+    strcpy(buffer,"x bad");
+    r = MsiGetSourcePath( hpkg, "TARGETDIR", buffer, &sz );
+    ok( r == ERROR_MORE_DATA, "return value wrong\n");
+    }
+    ok( buffer[0] == 'x', "buffer modified\n");
+
+    todo_wine {
+    r = MsiGetSourcePath( hpkg, "TARGETDIR", NULL, NULL );
+    ok( r == ERROR_SUCCESS, "return value wrong\n");
+    }
+
+    r = MsiGetSourcePath( hpkg, "TARGETDIR ", NULL, NULL );
+    ok( r == ERROR_DIRECTORY, "return value wrong\n");
+
+    r = MsiGetSourcePath( hpkg, "targetdir", NULL, NULL );
+    ok( r == ERROR_DIRECTORY, "return value wrong\n");
+
+    r = MsiGetSourcePath( hpkg, "TARGETDIR", buffer, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "return value wrong\n");
+
+    todo_wine {
+    r = MsiGetSourcePath( hpkg, "TARGETDIR", NULL, &sz );
+    ok( r == ERROR_SUCCESS, "return value wrong\n");
+    }
+
+    MsiCloseHandle( hpkg );
+    DeleteFile(msifile);
+}
+
+static void test_doaction( void )
+{
+    MSIHANDLE hpkg;
+    UINT r;
+
+    r = MsiDoAction( -1, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    hpkg = package_from_db(create_package_db());
+    ok( hpkg, "failed to create package\n");
+
+    r = MsiDoAction(hpkg, NULL);
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    r = MsiDoAction(0, "boo");
+    ok( r == ERROR_INVALID_HANDLE, "wrong return val\n");
+
+    r = MsiDoAction(hpkg, "boo");
+    ok( r == ERROR_FUNCTION_NOT_CALLED, "wrong return val\n");
+
+    MsiCloseHandle( hpkg );
+    DeleteFile(msifile);
+}
+
+static void test_gettargetpath_bad(void)
+{
+    char buffer[0x80];
+    MSIHANDLE hpkg;
+    DWORD sz;
+    UINT r;
+
+    hpkg = package_from_db(create_package_db());
+    ok( hpkg, "failed to create package\n");
+
+    r = MsiGetTargetPath( 0, NULL, NULL, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    r = MsiGetTargetPath( 0, NULL, NULL, &sz );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    r = MsiGetTargetPath( 0, "boo", NULL, NULL );
+    ok( r == ERROR_INVALID_HANDLE, "wrong return val\n");
+
+    r = MsiGetTargetPath( 0, "boo", NULL, NULL );
+    ok( r == ERROR_INVALID_HANDLE, "wrong return val\n");
+
+    r = MsiGetTargetPath( hpkg, "boo", NULL, NULL );
+    ok( r == ERROR_DIRECTORY, "wrong return val\n");
+
+    r = MsiGetTargetPath( hpkg, "boo", buffer, NULL );
+    ok( r == ERROR_DIRECTORY, "wrong return val\n");
+
+    MsiCloseHandle( hpkg );
+    DeleteFile(msifile);
+}
+
+static void query_file_path(MSIHANDLE hpkg, LPCSTR file, LPSTR buff)
+{
+    UINT r;
+    DWORD size;
+    MSIHANDLE rec;
+
+    rec = MsiCreateRecord( 1 );
+    ok(rec, "MsiCreate record failed\n");
+
+    r = MsiRecordSetString( rec, 0, file );
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r );
+
+    size = MAX_PATH;
+    r = MsiFormatRecord( hpkg, rec, buff, &size );
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %u\n", r );
+
+    MsiCloseHandle( rec );
+}
+
+static void test_settargetpath(void)
+{
+    char tempdir[MAX_PATH+8], buffer[MAX_PATH];
+    DWORD sz;
+    MSIHANDLE hpkg;
+    UINT r;
+    MSIHANDLE hdb;
+    
+    hdb = create_package_db();
+    ok ( hdb, "failed to create package database\n" );
+
+    r = add_directory_entry( hdb, "'TARGETDIR', '', 'SourceDir'" );
+    ok( r == S_OK, "failed to add directory entry: %d\n" , r );
+
+    r = run_query( hdb, /* these tables required by Windows Installer for MsiSetTargetPath */
+            "CREATE TABLE `Component` ( "
+            "`Component` CHAR(72) NOT NULL, "
+            "`ComponentId` CHAR(38), "
+            "`Directory_` CHAR(72) NOT NULL, "
+            "`Attributes` SHORT NOT NULL, "
+            "`Condition` CHAR(255), "
+            "`KeyPath` CHAR(72) "
+            "PRIMARY KEY `Component`)" );
+    ok( r == S_OK, "cannot create Component table: %d\n", r );
+
+    r = run_query( hdb,
+            "INSERT INTO `Component`  "
+            "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `Condition`, `KeyPath`) "
+            "VALUES( 'WinWorkAround', '{83e2694d-0864-4124-9323-6d37630912a1}', 'TARGETDIR', 8, '', 'FL_dummycomponent')" );
+    ok( r == S_OK, "cannot add dummy component: %d\n", r );
+
+    r = run_query( hdb,
+            "INSERT INTO `Component`  "
+            "(`Component`, `ComponentId`, `Directory_`, `Attributes`, `Condition`, `KeyPath`) "
+            "VALUES( 'TestComp', '{A3FB59C8-C293-4F7E-B8C5-F0E1D8EEE4E5}', 'TestDir', 0, '', 'TestFile')" );
+    ok( r == S_OK, "cannot add test component: %d\n", r );
+
+    r = run_query( hdb,
+            "CREATE TABLE `Feature` ( "
+            "`Feature` CHAR(38) NOT NULL, "
+            "`Feature_Parent` CHAR(38), "
+            "`Title` CHAR(64), "
+            "`Description` CHAR(255), "
+            "`Display` SHORT NOT NULL, "
+            "`Level` SHORT NOT NULL, "
+            "`Directory_` CHAR(72), "
+            "`Attributes` SHORT NOT NULL "
+            "PRIMARY KEY `Feature`)" );
+    ok( r == S_OK, "cannot create Feature table: %d\n", r );
+
+    r = run_query( hdb,
+            "INSERT INTO `Feature` "
+            "(`Feature`, `Feature_Parent`, `Display`, `Level`, `Attributes`) "
+            "VALUES( 'TestFeature', '', 0, 1, 0 )" );
+    ok( r == ERROR_SUCCESS, "cannot add TestFeature to Feature table: %d\n", r );
+
+    r = run_query( hdb,
+            "CREATE TABLE `FeatureComponents` ( "
+            "`Feature_` CHAR(38) NOT NULL, "
+            "`Component_` CHAR(72) NOT NULL "
+            "PRIMARY KEY `Feature_` )" );
+    ok( r == S_OK, "cannot create FeatureComponents table: %d\n", r );
+
+    r = run_query( hdb,
+            "INSERT INTO `FeatureComponents` "
+            "(`Feature_`, `Component_`) "
+            "VALUES( 'TestFeature', 'TestComp' )" );
+    ok( r == S_OK, "cannot insert component into FeatureComponents table: %d\n", r );
+
+    add_directory_entry( hdb, "'TestParent', 'TARGETDIR', 'TestParent'" );
+    add_directory_entry( hdb, "'TestDir', 'TestParent', 'TestDir'" );
+
+    r = run_query( hdb,
+            "CREATE TABLE `File` ("
+            "`File` CHAR(72) NOT NULL, "
+            "`Component_` CHAR(72) NOT NULL, "
+            "`FileName` CHAR(255) NOT NULL, "
+            "`FileSize` LONG NOT NULL, "
+            "`Version` CHAR(72), "
+            "`Language` CHAR(20), "
+            "`Attributes` SHORT, "
+            "`Sequence` SHORT NOT NULL "
+            "PRIMARY KEY `File`)" );
+    ok( r == S_OK, "cannot create File table: %d\n", r );
+
+    r = run_query( hdb,
+            "INSERT INTO `File` "
+            "(`File`, `Component_`, `FileName`, `FileSize`, `Version`, `Language`, `Attributes`, `Sequence`) "
+            "VALUES( 'TestFile', 'TestComp', 'testfile.txt', 0, '', '1033', 8192, 1 )" );
+    ok( r == S_OK, "cannot add file to the File table: %d\n", r );
+
+    hpkg = package_from_db( hdb );
+    ok( hpkg, "failed to create package\n");
+
+    r = MsiDoAction( hpkg, "CostInitialize");
+    ok( r == ERROR_SUCCESS, "cost init failed\n");
+
+    r = MsiDoAction( hpkg, "FileCost");
+    ok( r == ERROR_SUCCESS, "file cost failed\n");
+
+    r = MsiDoAction( hpkg, "CostFinalize");
+    ok( r == ERROR_SUCCESS, "cost finalize failed\n");
+
+    r = MsiSetTargetPath( 0, NULL, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    r = MsiSetTargetPath( 0, "boo", "C:\\bogusx" );
+    ok( r == ERROR_INVALID_HANDLE, "wrong return val\n");
+
+    r = MsiSetTargetPath( hpkg, "boo", NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    r = MsiSetTargetPath( hpkg, "boo", "c:\\bogusx" );
+    ok( r == ERROR_DIRECTORY, "wrong return val\n");
+
+    sz = sizeof tempdir - 1;
+    r = MsiGetTargetPath( hpkg, "TARGETDIR", tempdir, &sz );
+    if ( r == S_OK )
+    {
+        if ( GetTempFileName( tempdir, "_wt", 0, buffer ) )
+        {
+            sprintf( tempdir, "%s\\subdir", buffer );
+            r = MsiSetTargetPath( hpkg, "TARGETDIR", buffer );
+            ok( r == ERROR_SUCCESS, "MsiSetTargetPath on file returned %d\n", r );
+
+            r = MsiSetTargetPath( hpkg, "TARGETDIR", tempdir );
+            ok( r == ERROR_SUCCESS, "MsiSetTargetPath on 'subdir' of file returned %d\n", r );
+
+            DeleteFile( buffer );
+
+            r = MsiSetTargetPath( hpkg, "TARGETDIR", buffer );
+            ok( r == ERROR_SUCCESS, "MsiSetTargetPath returned %d\n", r );
+
+            r = GetFileAttributes( buffer );
+            ok ( r == INVALID_FILE_ATTRIBUTES, "file/directory exists after MsiSetTargetPath. Attributes: %08X\n", r );
+
+            r = MsiSetTargetPath( hpkg, "TARGETDIR", tempdir );
+            ok( r == ERROR_SUCCESS, "MsiSetTargetPath on subsubdir returned %d\n", r );
+        } else {
+            trace("GetTempFileName failed, cannot do some tests\n");
+        }
+    } else {
+        trace( "MsiGetTargetPath failed: %d\n", r );
+    }
+
+    r = MsiSetTargetPath( hpkg, "TestParent", "C:\\one\\two" );
+    ok( r == ERROR_SUCCESS, "MsiSetTargetPath returned %d\n", r );
+
+    query_file_path( hpkg, "[#TestFile]", buffer );
+    ok( !lstrcmp(buffer, "C:\\one\\two\\TestDir\\testfile.txt"),
+        "Expected C:\\one\\two\\TestDir\\testfile.txt, got %s\n", buffer );
+    
+    MsiCloseHandle( hpkg );
+}
+
+static void test_condition(void)
+{
+    MSICONDITION r;
+    MSIHANDLE hpkg;
+
+    hpkg = package_from_db(create_package_db());
+    ok( hpkg, "failed to create package\n");
+
+    r = MsiEvaluateCondition(0, NULL);
+    ok( r == MSICONDITION_ERROR, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, NULL);
+    ok( r == MSICONDITION_NONE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "");
+    ok( r == MSICONDITION_NONE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 = 0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 <> 0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 = 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 > 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 ~> 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 > 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 ~> 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 >= 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 ~>= 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 >= 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 ~>= 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 < 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 ~< 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 < 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 ~< 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 <= 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 ~<= 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 <= 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 ~<= 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 >=");
+    ok( r == MSICONDITION_ERROR, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " ");
+    ok( r == MSICONDITION_NONE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "LicView <> \"1\"");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "LicView <> \"0\"");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "LicView <> LicView");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "not 0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "not LicView");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "not \"A\"");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "~not \"A\"");
+    ok( r == MSICONDITION_ERROR, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "\"0\"");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 and 2");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "not 0 and 3");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "not 0 and 0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "not 0 or 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "(0)");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "(((((1))))))");
+    ok( r == MSICONDITION_ERROR, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "(((((1)))))");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " \"A\" < \"B\" ");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " \"A\" > \"B\" ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " \"1\" > \"12\" ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " \"100\" < \"21\" ");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 < > 0");
+    ok( r == MSICONDITION_ERROR, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "(1<<1) == 2");
+    ok( r == MSICONDITION_ERROR, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " \"A\" = \"a\" ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " \"A\" ~ = \"a\" ");
+    ok( r == MSICONDITION_ERROR, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " \"A\" ~= \"a\" ");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " \"A\" ~= 1 ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " \"A\" = 1 ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " 1 ~= 1 ");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " 1 ~= \"1\" ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " 1 = \"1\" ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " 0 = \"1\" ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " 0 < \"100\" ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, " 100 > \"0\" ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 XOR 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 IMP 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 IMP 0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 IMP 0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 EQV 0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 EQV 1");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 IMP 1 OR 0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 IMPL 1");
+    ok( r == MSICONDITION_ERROR, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "\"ASFD\" >< \"S\" ");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "\"ASFD\" ~>< \"s\" ");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "\"ASFD\" ~>< \"\" ");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "\"ASFD\" ~>< \"sss\" ");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "mm", "5" );
+
+    r = MsiEvaluateCondition(hpkg, "mm = 5");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "mm < 6");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "mm <= 5");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "mm > 4");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "mm < 12");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "mm = \"5\"");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 = \"\"");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 AND \"\"");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 AND \"\"");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "1 AND \"1\"");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "3 >< 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "3 >< 4");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "NOT 0 AND 0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "NOT 0 AND 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "NOT 1 OR 0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 AND 1 OR 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "0 AND 0 OR 1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "NOT 0 AND 1 OR 0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "_1 = _1");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "( 1 AND 1 ) = 2");
+    ok( r == MSICONDITION_ERROR, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "NOT ( 1 AND 1 )");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "NOT A AND (BBBBBBBBBB=2 OR CCC=1) AND Ddddddddd");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "Installed<>\"\"");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "NOT 1 AND 0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "bandalmael<>0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "bandalmael<0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "bandalmael>0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "bandalmael>=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "bandalmael<=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    r = MsiEvaluateCondition(hpkg, "bandalmael~<>0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "0" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "asdf" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "0asdf" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "0 " );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "-0" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "0000000000000" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "--0" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "0x00" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "-" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "+0" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+
+    MsiSetProperty(hpkg, "bandalmael", "0.0" );
+    r = MsiEvaluateCondition(hpkg, "bandalmael=0");
+    ok( r == MSICONDITION_FALSE, "wrong return val\n");
+    r = MsiEvaluateCondition(hpkg, "bandalmael<>0");
+    ok( r == MSICONDITION_TRUE, "wrong return val\n");
+
+    MsiCloseHandle( hpkg );
+    DeleteFile(msifile);
+}
+
+static BOOL check_prop_empty( MSIHANDLE hpkg, char * prop)
+{
+    UINT r;
+    DWORD sz;
+    char buffer[2];
+
+    sz = sizeof buffer;
+    strcpy(buffer,"x");
+    r = MsiGetProperty( hpkg, prop, buffer, &sz );
+    return r == ERROR_SUCCESS && buffer[0] == 0 && sz == 0;
+}
+
+static void test_props(void)
+{
+    MSIHANDLE hpkg;
+    UINT r;
+    DWORD sz;
+    char buffer[0x100];
+
+    hpkg = package_from_db(create_package_db());
+    ok( hpkg, "failed to create package\n");
+
+    /* test invalid values */
+    r = MsiGetProperty( 0, NULL, NULL, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    r = MsiGetProperty( hpkg, NULL, NULL, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    r = MsiGetProperty( hpkg, "boo", NULL, NULL );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+
+    r = MsiGetProperty( hpkg, "boo", buffer, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    /* test retrieving an empty/nonexistent property */
+    sz = sizeof buffer;
+    r = MsiGetProperty( hpkg, "boo", NULL, &sz );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+    ok( sz == 0, "wrong size returned\n");
+
+    check_prop_empty( hpkg, "boo");
+    sz = 0;
+    strcpy(buffer,"x");
+    r = MsiGetProperty( hpkg, "boo", buffer, &sz );
+    ok( r == ERROR_MORE_DATA, "wrong return val\n");
+    ok( !strcmp(buffer,"x"), "buffer was changed\n");
+    ok( sz == 0, "wrong size returned\n");
+
+    sz = 1;
+    strcpy(buffer,"x");
+    r = MsiGetProperty( hpkg, "boo", buffer, &sz );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+    ok( buffer[0] == 0, "buffer was not changed\n");
+    ok( sz == 0, "wrong size returned\n");
+
+    /* set the property to something */
+    r = MsiSetProperty( 0, NULL, NULL );
+    ok( r == ERROR_INVALID_HANDLE, "wrong return val\n");
+
+    r = MsiSetProperty( hpkg, NULL, NULL );
+    ok( r == ERROR_INVALID_PARAMETER, "wrong return val\n");
+
+    r = MsiSetProperty( hpkg, "", NULL );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+
+    /* try set and get some illegal property identifiers */
+    r = MsiSetProperty( hpkg, "", "asdf" );
+    ok( r == ERROR_FUNCTION_FAILED, "wrong return val\n");
+
+    r = MsiSetProperty( hpkg, "=", "asdf" );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+
+    r = MsiSetProperty( hpkg, " ", "asdf" );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+
+    r = MsiSetProperty( hpkg, "'", "asdf" );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+
+    sz = sizeof buffer;
+    buffer[0]=0;
+    r = MsiGetProperty( hpkg, "'", buffer, &sz );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+    ok( !strcmp(buffer,"asdf"), "buffer was not changed\n");
+
+    /* set empty values */
+    r = MsiSetProperty( hpkg, "boo", NULL );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+    ok( check_prop_empty( hpkg, "boo"), "prop wasn't empty\n");
+
+    r = MsiSetProperty( hpkg, "boo", "" );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+    ok( check_prop_empty( hpkg, "boo"), "prop wasn't empty\n");
+
+    /* set a non-empty value */
+    r = MsiSetProperty( hpkg, "boo", "xyz" );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+
+    sz = 1;
+    strcpy(buffer,"x");
+    r = MsiGetProperty( hpkg, "boo", buffer, &sz );
+    ok( r == ERROR_MORE_DATA, "wrong return val\n");
+    ok( buffer[0] == 0, "buffer was not changed\n");
+    ok( sz == 3, "wrong size returned\n");
+
+    sz = 4;
+    strcpy(buffer,"x");
+    r = MsiGetProperty( hpkg, "boo", buffer, &sz );
+    ok( r == ERROR_SUCCESS, "wrong return val\n");
+    ok( !strcmp(buffer,"xyz"), "buffer was not changed\n");
+    ok( sz == 3, "wrong size returned\n");
+
+    sz = 3;
+    strcpy(buffer,"x");
+    r = MsiGetProperty( hpkg, "boo", buffer, &sz );
+    ok( r == ERROR_MORE_DATA, "wrong return val\n");
+    ok( !strcmp(buffer,"xy"), "buffer was not changed\n");
+    ok( sz == 3, "wrong size returned\n");
+
+    MsiCloseHandle( hpkg );
+    DeleteFile(msifile);
+}
+
+static UINT try_query_param( MSIHANDLE hdb, LPCSTR szQuery, MSIHANDLE hrec )
+{
+    MSIHANDLE htab = 0;
+    UINT res;
+
+    res = MsiDatabaseOpenView( hdb, szQuery, &htab );
+    if( res == ERROR_SUCCESS )
+    {
+        UINT r;
+
+        r = MsiViewExecute( htab, hrec );
+        if( r != ERROR_SUCCESS )
+        {
+            res = r;
+            fprintf(stderr,"MsiViewExecute failed %08x\n", res);
+        }
+
+        r = MsiViewClose( htab );
+        if( r != ERROR_SUCCESS )
+            res = r;
+
+        r = MsiCloseHandle( htab );
+        if( r != ERROR_SUCCESS )
+            res = r;
+    }
+    return res;
+}
+
+static UINT try_query( MSIHANDLE hdb, LPCSTR szQuery )
+{
+    return try_query_param( hdb, szQuery, 0 );
+}
+
+static void test_msipackage(void)
+{
+    MSIHANDLE hdb = 0, hpack = 100;
+    UINT r;
+    const char *query;
+    char name[10];
+
+    DeleteFile(msifile);
+
+    todo_wine {
+    name[0] = 0;
+    r = MsiOpenPackage(name, &hpack);
+    ok(r == ERROR_SUCCESS, "failed to open package with no name\n");
+    r = MsiCloseHandle(hpack);
+    ok(r == ERROR_SUCCESS, "failed to close package\n");
+    }
+
+    /* just MsiOpenDatabase should not create a file */
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
+    ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
+
+    name[0]='#';
+    name[1]=0;
+    r = MsiOpenPackage(name, &hpack);
+    ok(r == ERROR_INVALID_HANDLE, "MsiOpenPackage returned wrong code\n");
+
+    todo_wine {
+    /* now try again with our empty database */
+    sprintf(name, "#%ld", hdb);
+    r = MsiOpenPackage(name, &hpack);
+    ok(r == ERROR_INSTALL_PACKAGE_INVALID, "MsiOpenPackage returned wrong code\n");
+    if (!r)    MsiCloseHandle(hpack);
+    }
+
+    /* create a table */
+    query = "CREATE TABLE `Property` ( "
+            "`Property` CHAR(72), `Value` CHAR(0) "
+            "PRIMARY KEY `Property`)";
+    r = try_query(hdb, query);
+    ok(r == ERROR_SUCCESS, "failed to create Properties table\n");
+
+    todo_wine {
+    query = "CREATE TABLE `InstallExecuteSequence` ("
+            "`Action` CHAR(72), `Condition` CHAR(0), `Sequence` INTEGER "
+            "PRIMARY KEY `Action`)";
+    r = try_query(hdb, query);
+    ok(r == ERROR_SUCCESS, "failed to create InstallExecuteSequence table\n");
+
+    sprintf(name, "#%ld", hdb);
+    r = MsiOpenPackage(name, &hpack);
+    ok(r == ERROR_INSTALL_PACKAGE_INVALID, "MsiOpenPackage returned wrong code\n");
+    if (!r)    MsiCloseHandle(hpack);
+    }
+
+    r = MsiCloseHandle(hdb);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle(database) failed\n");
+    DeleteFile(msifile);
+}
+
+static void test_formatrecord2(void)
+{
+    MSIHANDLE hpkg, hrec ;
+    char buffer[0x100];
+    DWORD sz;
+    UINT r;
+
+    hpkg = package_from_db(create_package_db());
+    ok( hpkg, "failed to create package\n");
+
+    r = MsiSetProperty(hpkg, "Manufacturer", " " );
+    ok( r == ERROR_SUCCESS, "set property failed\n");
+
+    hrec = MsiCreateRecord(2);
+    ok(hrec, "create record failed\n");
+
+    r = MsiRecordSetString( hrec, 0, "[ProgramFilesFolder][Manufacturer]\\asdf");
+    ok( r == ERROR_SUCCESS, "format record failed\n");
+
+    buffer[0] = 0;
+    sz = sizeof buffer;
+    r = MsiFormatRecord( hpkg, hrec, buffer, &sz );
+
+    r = MsiRecordSetString(hrec, 0, "[foo][1]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(hpkg, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"hoo"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "x[~]x");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(hpkg, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"x"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[foo.$%}][1]");
+    r = MsiRecordSetString(hrec, 1, "hoo");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(hpkg, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"hoo"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[\\[]");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(hpkg, hrec, buffer, &sz);
+    ok( sz == 1, "size wrong\n");
+    ok( 0 == strcmp(buffer,"["), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    SetEnvironmentVariable("FOO", "BAR");
+    r = MsiRecordSetString(hrec, 0, "[%FOO]");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(hpkg, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"BAR"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    r = MsiRecordSetString(hrec, 0, "[[1]]");
+    r = MsiRecordSetString(hrec, 1, "%FOO");
+    sz = sizeof buffer;
+    r = MsiFormatRecord(hpkg, hrec, buffer, &sz);
+    ok( sz == 3, "size wrong\n");
+    ok( 0 == strcmp(buffer,"BAR"), "wrong output %s\n",buffer);
+    ok( r == ERROR_SUCCESS, "format failed\n");
+
+    MsiCloseHandle( hrec );
+    MsiCloseHandle( hpkg );
+    DeleteFile(msifile);
+}
+
+static void test_states(void)
+{
+    MSIHANDLE hpkg;
+    UINT r;
+    MSIHANDLE hdb;
+    INSTALLSTATE state, action;
+
+    hdb = create_package_db();
+    ok ( hdb, "failed to create package database\n" );
+
+    r = add_directory_entry( hdb, "'TARGETDIR', '', 'SourceDir'");
+    ok( r == ERROR_SUCCESS, "cannot add directory: %d\n", r );
+
+    r = create_feature_table( hdb );
+    ok( r == ERROR_SUCCESS, "cannot create Feature table: %d\n", r );
+
+    r = create_component_table( hdb );
+    ok( r == ERROR_SUCCESS, "cannot create Component table: %d\n", r );
+
+    /* msidbFeatureAttributesFavorLocal */
+    r = add_feature_entry( hdb, "'one', '', 2, 1, 0" );
+    ok( r == ERROR_SUCCESS, "cannot add feature: %d\n", r );
+
+    /* msidbFeatureAttributesFavorLocal:msidbComponentAttributesLocalOnly */
+    r = add_component_entry( hdb, "'alpha', '{467EC132-739D-4784-A37B-677AA43DBC94}', 'TARGETDIR', 0, '', 'alpha_file'" );
+    ok( r == ERROR_SUCCESS, "cannot add component: %d\n", r );
+
+    /* msidbFeatureAttributesFavorLocal:msidbComponentAttributesSourceOnly */
+    r = add_component_entry( hdb, "'beta', '{2C1F189C-24A6-4C34-B26B-994A6C026506}', 'TARGETDIR', 1, '', 'beta_file'" );
+    ok( r == ERROR_SUCCESS, "cannot add component: %d\n", r );
+
+    /* msidbFeatureAttributesFavorLocal:msidbComponentAttributesOptional */
+    r = add_component_entry( hdb, "'gamma', '{C271E2A4-DE2E-4F70-86D1-6984AF7DE2CA}', 'TARGETDIR', 2, '', 'gamma_file'" );
+    ok( r == ERROR_SUCCESS, "cannot add component: %d\n", r );
+
+    /* msidbFeatureAttributesFavorSource */
+    r = add_feature_entry( hdb, "'two', '', 2, 1, 1" );
+    ok( r == ERROR_SUCCESS, "cannot add feature: %d\n", r );
+
+    /* msidbFeatureAttributesFavorSource:msidbComponentAttributesLocalOnly */
+    r = add_component_entry( hdb, "'delta', '{938FD4F2-C648-4259-A03C-7AA3B45643F3}', 'TARGETDIR', 0, '', 'delta_file'" );
+    ok( r == ERROR_SUCCESS, "cannot add component: %d\n", r );
+
+    /* msidbFeatureAttributesFavorSource:msidbComponentAttributesSourceOnly */
+    r = add_component_entry( hdb, "'epsilon', '{D59713B6-C11D-47F2-A395-1E5321781190}', 'TARGETDIR', 1, '', 'epsilon_file'" );
+    ok( r == ERROR_SUCCESS, "cannot add component: %d\n", r );
+
+    /* msidbFeatureAttributesFavorSource:msidbComponentAttributesOptional */
+    r = add_component_entry( hdb, "'zeta', '{377D33AB-2FAA-42B9-A629-0C0DAE9B9C7A}', 'TARGETDIR', 2, '', 'zeta_file'" );
+    ok( r == ERROR_SUCCESS, "cannot add component: %d\n", r );
+
+    /* msidbFeatureAttributesFavorSource */
+    r = add_feature_entry( hdb, "'three', '', 2, 1, 1" );
+    ok( r == ERROR_SUCCESS, "cannot add feature: %d\n", r );
+
+    /* msidbFeatureAttributesFavorSource:msidbComponentAttributesSourceOnly */
+    r = add_component_entry( hdb, "'eta', '{DD89003F-0DD4-41B8-81C0-3411A7DA2695}', 'TARGETDIR', 1, '', 'eta_file'" );
+    ok( r == ERROR_SUCCESS, "cannot add component: %d\n", r );
+
+    r = create_feature_components_table( hdb );
+    ok( r == ERROR_SUCCESS, "cannot create FeatureComponents table: %d\n", r );
+
+    r = add_feature_components_entry( hdb, "'one', 'alpha'" );
+    ok( r == ERROR_SUCCESS, "cannot add feature components: %d\n", r );
+
+    r = add_feature_components_entry( hdb, "'one', 'beta'" );
+    ok( r == ERROR_SUCCESS, "cannot add feature components: %d\n", r );
+
+    r = add_feature_components_entry( hdb, "'one', 'gamma'" );
+    ok( r == ERROR_SUCCESS, "cannot add feature components: %d\n", r );
+
+    r = add_feature_components_entry( hdb, "'two', 'delta'" );
+    ok( r == ERROR_SUCCESS, "cannot add feature components: %d\n", r );
+
+    r = add_feature_components_entry( hdb, "'two', 'epsilon'" );
+    ok( r == ERROR_SUCCESS, "cannot add feature components: %d\n", r );
+
+    r = add_feature_components_entry( hdb, "'two', 'zeta'" );
+    ok( r == ERROR_SUCCESS, "cannot add feature components: %d\n", r );
+
+    r = add_feature_components_entry( hdb, "'three', 'eta'" );
+    ok( r == ERROR_SUCCESS, "cannot add feature components: %d\n", r );
+
+    r = create_file_table( hdb );
+    ok( r == ERROR_SUCCESS, "cannot create File table: %d\n", r );
+
+    r = add_file_entry( hdb, "'alpha_file', 'alpha', 'alpha.txt', 100, '', '1033', 8192, 1" );
+    ok( r == ERROR_SUCCESS, "cannot add file: %d\n", r);
+
+    r = add_file_entry( hdb, "'beta_file', 'beta', 'beta.txt', 0, '', '1033', 8192, 1" );
+    ok( r == ERROR_SUCCESS, "cannot add file: %d\n", r);
+
+    r = add_file_entry( hdb, "'gamma_file', 'gamma', 'gamma.txt', 0, '', '1033', 8192, 1" );
+    ok( r == ERROR_SUCCESS, "cannot add file: %d\n", r);
+
+    r = add_file_entry( hdb, "'delta_file', 'delta', 'delta.txt', 0, '', '1033', 8192, 1" );
+    ok( r == ERROR_SUCCESS, "cannot add file: %d\n", r);
+
+    r = add_file_entry( hdb, "'epsilon_file', 'epsilon', 'epsilon.txt', 0, '', '1033', 8192, 1" );
+    ok( r == ERROR_SUCCESS, "cannot add file: %d\n", r);
+
+    r = add_file_entry( hdb, "'zeta_file', 'zeta', 'zeta.txt', 0, '', '1033', 8192, 1" );
+    ok( r == ERROR_SUCCESS, "cannot add file: %d\n", r);
+
+    /* compressed file */
+    r = add_file_entry( hdb, "'eta_file', 'eta', 'eta.txt', 0, '', '1033', 16384, 1" );
+    ok( r == ERROR_SUCCESS, "cannot add file: %d\n", r);
+
+    hpkg = package_from_db( hdb );
+    ok( hpkg, "failed to create package\n");
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "one", &state, &action);
+    ok( r == ERROR_UNKNOWN_FEATURE, "Expected ERROR_UNKNOWN_FEATURE, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "two", &state, &action);
+    ok( r == ERROR_UNKNOWN_FEATURE, "Expected ERROR_UNKNOWN_FEATURE, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "three", &state, &action);
+    ok( r == ERROR_UNKNOWN_FEATURE, "Expected ERROR_UNKNOWN_FEATURE, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "alpha", &state, &action);
+    ok( r == ERROR_UNKNOWN_COMPONENT, "Expected ERROR_UNKNOWN_COMPONENT, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "beta", &state, &action);
+    ok( r == ERROR_UNKNOWN_COMPONENT, "Expected ERROR_UNKNOWN_COMPONENT, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "gamma", &state, &action);
+    ok( r == ERROR_UNKNOWN_COMPONENT, "Expected ERROR_UNKNOWN_COMPONENT, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "delta", &state, &action);
+    ok( r == ERROR_UNKNOWN_COMPONENT, "Expected ERROR_UNKNOWN_COMPONENT, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "epsilon", &state, &action);
+    ok( r == ERROR_UNKNOWN_COMPONENT, "Expected ERROR_UNKNOWN_COMPONENT, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "zeta", &state, &action);
+    ok( r == ERROR_UNKNOWN_COMPONENT, "Expected ERROR_UNKNOWN_COMPONENT, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "eta", &state, &action);
+    ok( r == ERROR_UNKNOWN_COMPONENT, "Expected ERROR_UNKNOWN_COMPONENT, got %d\n", r );
+    ok( state == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", state);
+    ok( action == 0xdeadbeef, "Expected 0xdeadbeef, got %d\n", action);
+
+    r = MsiDoAction( hpkg, "CostInitialize");
+    ok( r == ERROR_SUCCESS, "cost init failed\n");
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "one", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+    }
+    ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "two", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+    }
+    ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "three", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+    }
+    ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "alpha", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "beta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "gamma", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "delta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "epsilon", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "zeta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "eta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    r = MsiDoAction( hpkg, "FileCost");
+    ok( r == ERROR_SUCCESS, "file cost failed\n");
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "one", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+    }
+    ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "two", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+    }
+    ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "three", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+    }
+    ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "alpha", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "beta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "gamma", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "delta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "epsilon", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "zeta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "eta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    todo_wine
+    {
+        ok( state == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", state);
+        ok( action == INSTALLSTATE_UNKNOWN, "Expected INSTALLSTATE_UNKNOWN, got %d\n", action);
+    }
+
+    r = MsiDoAction( hpkg, "CostFinalize");
+    ok( r == ERROR_SUCCESS, "cost finalize failed: %d\n", r);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "one", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    todo_wine
+    {
+        ok( action == INSTALLSTATE_LOCAL, "Expected INSTALLSTATE_LOCAL, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "two", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    todo_wine
+    {
+        ok( action == INSTALLSTATE_SOURCE, "Expected INSTALLSTATE_SOURCE, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetFeatureState(hpkg, "three", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    todo_wine
+    {
+        ok( action == INSTALLSTATE_LOCAL, "Expected INSTALLSTATE_LOCAL, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "alpha", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    ok( action == INSTALLSTATE_LOCAL, "Expected INSTALLSTATE_LOCAL, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "beta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    ok( action == INSTALLSTATE_SOURCE, "Expected INSTALLSTATE_SOURCE, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "gamma", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    ok( action == INSTALLSTATE_LOCAL, "Expected INSTALLSTATE_LOCAL, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "delta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    ok( action == INSTALLSTATE_LOCAL, "Expected INSTALLSTATE_LOCAL, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "epsilon", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    ok( action == INSTALLSTATE_SOURCE, "Expected INSTALLSTATE_SOURCE, got %d\n", action);
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "zeta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    todo_wine
+    {
+        ok( action == INSTALLSTATE_SOURCE, "Expected INSTALLSTATE_SOURCE, got %d\n", action);
+    }
+
+    state = 0xdeadbeef;
+    action = 0xdeadbeef;
+    r = MsiGetComponentState(hpkg, "eta", &state, &action);
+    ok( r == ERROR_SUCCESS, "Expected ERROR_SUCCESS, got %d\n", r );
+    ok( state == INSTALLSTATE_ABSENT, "Expected INSTALLSTATE_ABSENT, got %d\n", state);
+    todo_wine
+    {
+        ok( action == INSTALLSTATE_LOCAL, "Expected INSTALLSTATE_LOCAL, got %d\n", action);
+    }
+    
+    MsiCloseHandle( hpkg );
+}
+
+START_TEST(package)
+{
+    test_createpackage();
+    test_getsourcepath_bad();
+    test_getsourcepath();
+    test_doaction();
+    test_gettargetpath_bad();
+    test_settargetpath();
+    test_props();
+    test_condition();
+    test_msipackage();
+    test_formatrecord2();
+    test_states();
+}
diff --git a/reactos/regtests/winetests/msi/record.c b/reactos/regtests/winetests/msi/record.c
new file mode 100644 (file)
index 0000000..2cfc71e
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2005 Mike McCormack for CodeWeavers
+ *
+ * A test program for MSI records
+ *
+ * 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 <windows.h>
+#include <msi.h>
+#include <msiquery.h>
+
+#include "wine/test.h"
+#include "wine/windef.h"
+
+static BOOL create_temp_file(char *name)
+{
+    UINT r;
+    unsigned char buffer[26], i;
+    DWORD sz;
+    HANDLE handle;
+    
+    r = GetTempFileName(".", "msitest",0,name);
+    if(!r)
+        return r;
+    handle = CreateFile(name, GENERIC_READ|GENERIC_WRITE, 
+        0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+    if(handle==INVALID_HANDLE_VALUE)
+        return 0;
+    for(i=0; i<26; i++)
+        buffer[i]=i+'a';
+    r = WriteFile(handle,buffer,sizeof buffer,&sz,NULL);
+    CloseHandle(handle);
+    return r;
+}
+
+static void test_msirecord(void)
+{
+    DWORD r, sz;
+    INT i;
+    MSIHANDLE h;
+    char buf[10];
+    WCHAR bufW[10];
+    const char str[] = "hello";
+    const WCHAR strW[] = { 'h','e','l','l','o',0};
+    char filename[MAX_PATH];
+
+    /* check behaviour with an invalid record */
+    r = MsiRecordGetFieldCount(0);
+    ok(r==-1, "field count for invalid record not -1\n");
+    SetLastError(0);
+    r = MsiRecordIsNull(0, 0);
+    ok(r==0, "invalid handle not considered to be non-null...\n");
+    ok(GetLastError()==0, "MsiRecordIsNull set LastError\n");
+    r = MsiRecordGetInteger(0,0);
+    ok(r == MSI_NULL_INTEGER, "got integer from invalid record\n");
+    r = MsiRecordSetInteger(0,0,0);
+    ok(r == ERROR_INVALID_HANDLE, "MsiRecordSetInteger returned wrong error\n");
+    r = MsiRecordSetInteger(0,-1,0);
+    ok(r == ERROR_INVALID_HANDLE, "MsiRecordSetInteger returned wrong error\n");
+    SetLastError(0);
+    h = MsiCreateRecord(-1);
+    ok(h==0, "created record with -1 elements\n");
+    h = MsiCreateRecord(0x10000);
+    ok(h==0, "created record with 0x10000 elements\n");
+    /* doesn't set LastError */
+    ok(GetLastError()==0, "MsiCreateRecord set last error\n");
+    r = MsiRecordClearData(0);
+    ok(r == ERROR_INVALID_HANDLE, "MsiRecordClearData returned wrong error\n");
+    r = MsiRecordDataSize(0,0);
+    ok(r == 0, "MsiRecordDataSize returned wrong error\n");
+
+
+    /* check behaviour of a record with 0 elements */
+    h = MsiCreateRecord(0);
+    ok(h!=0, "couldn't create record with zero elements\n");
+    r = MsiRecordGetFieldCount(h);
+    ok(r==0, "field count should be zero\n");
+    r = MsiRecordIsNull(h,0);
+    ok(r, "new record wasn't null\n");
+    r = MsiRecordIsNull(h,1);
+    ok(r, "out of range record wasn't null\n");
+    r = MsiRecordIsNull(h,-1);
+    ok(r, "out of range record wasn't null\n");
+    r = MsiRecordDataSize(h,0);
+    ok(r==0, "size of null record is 0\n");
+    sz = sizeof buf;
+    strcpy(buf,"x");
+    r = MsiRecordGetString(h, 0, buf, &sz);
+    ok(r==ERROR_SUCCESS, "failed to get null string\n");
+    ok(sz==0, "null string too long\n");
+    ok(buf[0]==0, "null string not set\n");
+
+    /* same record, but add an integer to it */
+    r = MsiRecordSetInteger(h, 0, 0);
+    ok(r == ERROR_SUCCESS, "Failed to set integer at 0 to 0\n");
+    r = MsiRecordIsNull(h,0);
+    ok(r==0, "new record is null after setting an integer\n");
+    r = MsiRecordDataSize(h,0);
+    ok(r==sizeof(DWORD), "size of integer record is 4\n");
+    r = MsiRecordSetInteger(h, 0, 1);
+    ok(r == ERROR_SUCCESS, "Failed to set integer at 0 to 1\n");
+    r = MsiRecordSetInteger(h, 1, 1);
+    ok(r == ERROR_INVALID_PARAMETER, "set integer at 1\n");
+    r = MsiRecordSetInteger(h, -1, 0);
+    ok(r == ERROR_INVALID_PARAMETER, "set integer at -1\n");
+    r = MsiRecordIsNull(h,0);
+    ok(r==0, "new record is null after setting an integer\n");
+    r = MsiRecordGetInteger(h, 0);
+    ok(r == 1, "failed to get integer\n");
+
+    /* same record, but add a string to it */
+    r = MsiRecordSetString(h, 0, NULL);
+    ok(r == ERROR_SUCCESS, "Failed to set null string at 0\n");
+    r = MsiRecordIsNull(h, 0);
+    ok(r == TRUE, "null string not null field\n");
+    r = MsiRecordSetString(h, 0, "");
+    ok(r == ERROR_SUCCESS, "Failed to set empty string at 0\n");
+    r = MsiRecordIsNull(h, 0);
+    ok(r == TRUE, "null string not null field\n");
+    r = MsiRecordSetString(h,0,str);
+    ok(r == ERROR_SUCCESS, "Failed to set string at 0\n");
+    r = MsiRecordGetInteger(h, 0);
+    ok(r == MSI_NULL_INTEGER, "should get invalid integer\n");
+    r = MsiRecordDataSize(h,0);
+    ok(r==sizeof str-1, "size of string record is strlen\n");
+    buf[0]=0;
+    sz = sizeof buf;
+    r = MsiRecordGetString(h,0,buf,&sz);
+    ok(r == ERROR_SUCCESS, "Failed to get string at 0\n");
+    ok(0==strcmp(buf,str), "MsiRecordGetString returned the wrong string\n");
+    ok(sz == sizeof str-1, "MsiRecordGetString returned the wrong length\n");
+    buf[0]=0;
+    sz = sizeof str - 2;
+    r = MsiRecordGetString(h,0,buf,&sz);
+    ok(r == ERROR_MORE_DATA, "small buffer should yield ERROR_MORE_DATA\n");
+    ok(sz == sizeof str-1, "MsiRecordGetString returned the wrong length\n");
+    ok(0==strncmp(buf,str,sizeof str-3), "MsiRecordGetString returned the wrong string\n");
+    ok(buf[sizeof str - 3]==0, "string wasn't nul terminated\n");
+
+    buf[0]=0;
+    sz = sizeof str;
+    r = MsiRecordGetString(h,0,buf,&sz);
+    ok(r == ERROR_SUCCESS, "wrong error\n");
+    ok(sz == sizeof str-1, "MsiRecordGetString returned the wrong length\n");
+    ok(0==strcmp(buf,str), "MsiRecordGetString returned the wrong string\n");
+
+
+    memset(bufW, 0, sizeof bufW);
+    sz = 5;
+    r = MsiRecordGetStringW(h,0,bufW,&sz);
+    ok(r == ERROR_MORE_DATA, "wrong error\n");
+    ok(sz == 5, "MsiRecordGetString returned the wrong length\n");
+    ok(0==memcmp(bufW,strW,8), "MsiRecordGetString returned the wrong string\n");
+
+    sz = 0;
+    bufW[0] = 'x';
+    r = MsiRecordGetStringW(h,0,bufW,&sz);
+    ok(r == ERROR_MORE_DATA, "wrong error\n");
+    ok(sz == 5, "MsiRecordGetString returned the wrong length\n");
+    ok('x'==bufW[0], "MsiRecordGetString returned the wrong string\n");
+
+    memset(buf, 0, sizeof buf);
+    sz = 5;
+    r = MsiRecordGetStringA(h,0,buf,&sz);
+    ok(r == ERROR_MORE_DATA, "wrong error\n");
+    ok(sz == 5, "MsiRecordGetString returned the wrong length\n");
+    ok(0==memcmp(buf,str,4), "MsiRecordGetString returned the wrong string\n");
+
+    sz = 0;
+    buf[0] = 'x';
+    r = MsiRecordGetStringA(h,0,buf,&sz);
+    ok(r == ERROR_MORE_DATA, "wrong error\n");
+    ok(sz == 5, "MsiRecordGetString returned the wrong length\n");
+    ok('x'==buf[0], "MsiRecordGetString returned the wrong string\n");
+
+    /* same record, check we can wipe all the data */
+    r = MsiRecordClearData(h);
+    ok(r == ERROR_SUCCESS, "Failed to clear record\n");
+    r = MsiRecordClearData(h);
+    ok(r == ERROR_SUCCESS, "Failed to clear record again\n");
+    r = MsiRecordIsNull(h,0);
+    ok(r, "cleared record wasn't null\n");
+
+    /* same record, try converting strings to integers */
+    i = MsiRecordSetString(h,0,"42");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == 42, "should get invalid integer\n");
+    i = MsiRecordSetString(h,0,"-42");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == -42, "should get invalid integer\n");
+    i = MsiRecordSetString(h,0," 42");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == MSI_NULL_INTEGER, "should get invalid integer\n");
+    i = MsiRecordSetString(h,0,"42 ");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == MSI_NULL_INTEGER, "should get invalid integer\n");
+    i = MsiRecordSetString(h,0,"42.0");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == MSI_NULL_INTEGER, "should get invalid integer\n");
+    i = MsiRecordSetString(h,0,"0x42");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == MSI_NULL_INTEGER, "should get invalid integer\n");
+    i = MsiRecordSetString(h,0,"1000000000000000");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == -1530494976, "should get truncated integer\n");
+    i = MsiRecordSetString(h,0,"2147483647");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == 2147483647, "should get maxint\n");
+    i = MsiRecordSetString(h,0,"-2147483647");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == -2147483647, "should get -maxint-1\n");
+    i = MsiRecordSetString(h,0,"4294967297");
+    ok(i == ERROR_SUCCESS, "Failed to set string at 0\n");
+    i = MsiRecordGetInteger(h, 0);
+    ok(i == 1, "should get one\n");
+
+    /* same record, try converting integers to strings */
+    r = MsiRecordSetInteger(h, 0, 32);
+    ok(r == ERROR_SUCCESS, "Failed to set integer at 0 to 32\n");
+    buf[0]=0;
+    sz = sizeof buf;
+    r = MsiRecordGetString(h, 0, buf, &sz);
+    ok(r == ERROR_SUCCESS, "failed to get string from integer\n");
+    ok(0==strcmp(buf,"32"), "failed to get string from integer\n");
+    r = MsiRecordSetInteger(h, 0, -32);
+    ok(r == ERROR_SUCCESS, "Failed to set integer at 0 to 32\n");
+    buf[0]=0;
+    sz = sizeof buf;
+    r = MsiRecordGetString(h, 0, buf, &sz);
+    ok(r == ERROR_SUCCESS, "failed to get string from integer\n");
+    ok(0==strcmp(buf,"-32"), "failed to get string from integer\n");
+
+    /* same record, now try streams */
+    r = MsiRecordSetStream(h, 0, NULL);
+    ok(r == ERROR_INVALID_PARAMETER, "set NULL stream\n");
+    sz = sizeof buf;
+    r = MsiRecordReadStream(h, 0, buf, &sz);
+    ok(r == ERROR_INVALID_DATATYPE, "read non-stream type\n");
+    ok(sz == sizeof buf, "set sz\n");
+    r = MsiRecordDataSize( h, -1);
+    ok(r == 0,"MsiRecordDataSize returned wrong size\n");
+    r = MsiRecordDataSize( h, 0);
+    ok(r == 4,"MsiRecordDataSize returned wrong size\n");
+
+    /* same record, now close it */
+    r = MsiCloseHandle(h);
+    ok(r == ERROR_SUCCESS, "Failed to close handle\n");
+
+    /* now try streams in a new record - need to create a file to play with */
+    r = create_temp_file(filename); 
+    if(!r)
+        return;
+
+    /* streams can't be inserted in field 0 for some reason */
+    h = MsiCreateRecord(2);
+    ok(h, "couldn't create a two field record\n");
+    r = MsiRecordSetStream(h, 0, filename);
+    ok(r == ERROR_INVALID_PARAMETER, "added stream to field 0\n");
+    r = MsiRecordSetStream(h, 1, filename);
+    ok(r == ERROR_SUCCESS, "failed to add stream to record\n");
+    r = MsiRecordReadStream(h, 1, buf, NULL);
+    ok(r == ERROR_INVALID_PARAMETER, "should return error\n");
+    /* http://test.winehq.org/data/200503181000/98_jmelgarejo98casa/msi:record.txt */
+    DeleteFile(filename); /* Windows 98 doesn't like this at all, so don't check return. */
+    r = MsiRecordReadStream(h, 1, NULL, NULL);
+    ok(r == ERROR_INVALID_PARAMETER, "should return error\n");
+    sz = sizeof buf;
+    r = MsiRecordReadStream(h, 1, NULL, &sz);
+    ok(r == ERROR_SUCCESS, "failed to read stream\n");
+    ok(sz==26,"couldn't get size of stream\n");
+    sz = 0;
+    r = MsiRecordReadStream(h, 1, buf, &sz);
+    ok(r == ERROR_SUCCESS, "failed to read stream\n");
+    ok(sz==0,"short read\n");
+    sz = sizeof buf;
+    r = MsiRecordReadStream(h, 1, buf, &sz);
+    ok(r == ERROR_SUCCESS, "failed to read stream\n");
+    ok(sz==sizeof buf,"short read\n");
+    ok(!strncmp(buf,"abcdefghij",10), "read the wrong thing\n");
+    sz = sizeof buf;
+    r = MsiRecordReadStream(h, 1, buf, &sz);
+    ok(r == ERROR_SUCCESS, "failed to read stream\n");
+    ok(sz==sizeof buf,"short read\n");
+    ok(!strncmp(buf,"klmnopqrst",10), "read the wrong thing\n");
+    memset(buf,0,sizeof buf);
+    sz = sizeof buf;
+    r = MsiRecordReadStream(h, 1, buf, &sz);
+    ok(r == ERROR_SUCCESS, "failed to read stream\n");
+    ok(sz==6,"short read\n");
+    ok(!strcmp(buf,"uvwxyz"), "read the wrong thing\n");
+    memset(buf,0,sizeof buf);
+    sz = sizeof buf;
+    r = MsiRecordReadStream(h, 1, buf, &sz);
+    ok(r == ERROR_SUCCESS, "failed to read stream\n");
+    ok(sz==0,"size non-zero at end of stream\n");
+    ok(buf[0]==0, "read something at end of the stream\n");
+    r = MsiRecordSetStream(h, 1, NULL);
+    ok(r == ERROR_SUCCESS, "failed to reset stream\n");
+    sz = 0;
+    r = MsiRecordReadStream(h, 1, NULL, &sz);
+    ok(r == ERROR_SUCCESS, "bytes left wrong after reset\n");
+    ok(sz==26,"couldn't get size of stream\n");
+    r = MsiRecordDataSize(h,1);
+    ok(r == 26,"MsiRecordDataSize returned wrong size\n");
+
+    /* now close the stream record */
+    r = MsiCloseHandle(h);
+    ok(r == ERROR_SUCCESS, "Failed to close handle\n");
+    DeleteFile(filename); /* Delete it for sure, when everything else is closed. */
+}
+
+START_TEST(record)
+{
+    test_msirecord();
+}
diff --git a/reactos/regtests/winetests/msi/suminfo.c b/reactos/regtests/winetests/msi/suminfo.c
new file mode 100644 (file)
index 0000000..b5b1150
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2005 Mike McCormack for CodeWeavers
+ *
+ * A test program for MSI database files.
+ *
+ * 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
+ */
+
+#define COBJMACROS
+
+#include <stdio.h>
+#include <windows.h>
+#include <msi.h>
+#include <msiquery.h>
+
+#include "wine/test.h"
+#include "wine/windef.h"
+
+/*
+ * The following are defined in Windows SDK's msidefs.h
+ * but that file doesn't exist in the msvc6 header set.
+ *
+ * Some are already defined in PropIdl.h - undefine them
+ */
+#undef PID_DICTIONARY
+#undef PID_CODEPAGE
+#undef PID_SUBJECT
+#undef PID_SECURITY
+
+#define PID_DICTIONARY 0
+#define PID_CODEPAGE 1
+#define PID_TITLE 2
+#define PID_SUBJECT 3
+#define PID_AUTHOR 4
+#define PID_KEYWORDS 5
+#define PID_COMMENTS 6
+#define PID_TEMPLATE 7
+#define PID_LASTAUTHOR 8
+#define PID_REVNUMBER 9
+#define PID_EDITTINE 10
+#define PID_LASTPRINTED 11
+#define PID_CREATE_DTM 12
+#define PID_LASTSAVE_DTM 13
+#define PID_PAGECOUNT 14
+#define PID_WORDCOUNT 15
+#define PID_CHARCOUNT 16
+#define PID_THUMBNAIL 17
+#define PID_APPNAME 18
+#define PID_SECURITY 19
+#define PID_MSIVERSION PID_PAGECOUNT
+#define PID_MSISOURCE PID_WORDCOUNT
+#define PID_MSIRESTRICT PID_CHARCOUNT
+
+START_TEST(suminfo)
+{
+    const char *msifile = "winetest.msi";
+    MSIHANDLE hdb = 0, hsuminfo;
+    UINT r, count, type;
+    DWORD sz;
+    INT val;
+    FILETIME ft;
+    char buf[0x10];
+
+    DeleteFile(msifile);
+
+    /* just MsiOpenDatabase should not create a file */
+    r = MsiOpenDatabase(msifile, MSIDBOPEN_CREATE, &hdb);
+    ok(r == ERROR_SUCCESS, "MsiOpenDatabase failed\n");
+
+    r = MsiGetSummaryInformation(hdb, NULL, 0, NULL);
+    ok(r == ERROR_INVALID_PARAMETER, "MsiGetSummaryInformation wrong error\n");
+
+    r = MsiGetSummaryInformation(hdb, NULL, 0, &hsuminfo);
+    ok(r == ERROR_SUCCESS, "MsiGetSummaryInformation failed\n");
+
+    r = MsiSummaryInfoGetPropertyCount(0, NULL);
+    ok(r == ERROR_INVALID_HANDLE, "getpropcount failed\n");
+
+    r = MsiSummaryInfoGetPropertyCount(hsuminfo, NULL);
+    ok(r == ERROR_SUCCESS, "getpropcount failed\n");
+
+    count = -1;
+    r = MsiSummaryInfoGetPropertyCount(hsuminfo, &count);
+    ok(r == ERROR_SUCCESS, "getpropcount failed\n");
+    ok(count == 0, "count should be zero\n");
+
+    r = MsiSummaryInfoGetProperty(hsuminfo, 0, NULL, NULL, NULL, 0, NULL);
+    ok(r == ERROR_SUCCESS, "getpropcount failed\n");
+
+    type = -1;
+    r = MsiSummaryInfoGetProperty(hsuminfo, 0, &type, NULL, NULL, 0, NULL);
+    ok(r == ERROR_SUCCESS, "getpropcount failed\n");
+    ok(type == 0, "wrong type\n");
+
+    type = -1;
+    val = 1234;
+    r = MsiSummaryInfoGetProperty(hsuminfo, 0, &type, &val, NULL, 0, NULL);
+    ok(r == ERROR_SUCCESS, "getpropcount failed\n");
+    ok(type == 0, "wrong type\n");
+    ok(val == 1234, "wrong val\n");
+
+    buf[0]='x';
+    buf[1]=0;
+    sz = 0x10;
+    r = MsiSummaryInfoGetProperty(hsuminfo, PID_REVNUMBER, &type, &val, NULL, buf, &sz);
+    ok(r == ERROR_SUCCESS, "getpropcount failed\n");
+    ok(buf[0]=='x', "cleared buffer\n");
+    ok(sz == 0x10, "count wasn't zero\n");
+    ok(type == VT_EMPTY, "should be empty\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_TITLE, VT_LPSTR, 0, NULL, "Mike");
+    ok(r == ERROR_FUNCTION_FAILED, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_TITLE, VT_LPSTR, 1, NULL, "JungAh");
+    ok(r == ERROR_FUNCTION_FAILED, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_TITLE, VT_LPSTR, 1, &ft, "Mike");
+    ok(r == ERROR_FUNCTION_FAILED, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_CODEPAGE, VT_I2, 1, &ft, "JungAh");
+    ok(r == ERROR_FUNCTION_FAILED, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiCloseHandle(hsuminfo);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    /* try again with the update count set */
+    r = MsiGetSummaryInformation(hdb, NULL, 1, &hsuminfo);
+    ok(r == ERROR_SUCCESS, "MsiGetSummaryInformation failed\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, 0, VT_LPSTR, 1, NULL, NULL);
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_CODEPAGE, VT_LPSTR, 1, NULL, NULL);
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_TITLE, VT_I4, 0, NULL, "Mike");
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_AUTHOR, VT_I4, 0, NULL, "JungAh");
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_KEYWORDS, VT_I2, 0, NULL, "Mike");
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_COMMENTS, VT_FILETIME, 0, NULL, "JungAh");
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_TEMPLATE, VT_I2, 0, NULL, "Mike");
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_LASTAUTHOR, VT_LPSTR, 0, NULL, NULL);
+    ok(r == ERROR_INVALID_PARAMETER, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_LASTSAVE_DTM, VT_FILETIME, 0, NULL, NULL);
+    ok(r == ERROR_INVALID_PARAMETER, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_LASTAUTHOR, VT_LPWSTR, 0, NULL, "h\0i\0\0");
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_REVNUMBER, VT_I4, 0, NULL, "Jungah");
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_PAGECOUNT, VT_LPSTR, 1, NULL, NULL);
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_TITLE, VT_LPSTR, 0, NULL, "Mike");
+    ok(r == ERROR_SUCCESS, "MsiSummaryInfoSetProperty failed\n");
+
+    sz = 2;
+    strcpy(buf,"x");
+    r = MsiSummaryInfoGetProperty(hsuminfo, PID_TITLE, &type, NULL, NULL, buf, &sz );
+    ok(r == ERROR_MORE_DATA, "MsiSummaryInfoSetProperty failed\n");
+    ok(sz == 4, "count was wrong\n");
+    ok(type == VT_LPSTR, "type was wrong\n");
+    ok(!strcmp(buf,"M"), "buffer was wrong\n");
+
+    sz = 4;
+    strcpy(buf,"x");
+    r = MsiSummaryInfoGetProperty(hsuminfo, PID_TITLE, &type, NULL, NULL, buf, &sz );
+    ok(r == ERROR_MORE_DATA, "MsiSummaryInfoSetProperty failed\n");
+    ok(sz == 4, "count was wrong\n");
+    ok(type == VT_LPSTR, "type was wrong\n");
+    ok(!strcmp(buf,"Mik"), "buffer was wrong\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_TITLE, VT_LPSTR, 0, NULL, "JungAh");
+    ok(r == ERROR_SUCCESS, "MsiSummaryInfoSetProperty failed\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_CODEPAGE, VT_I2, 1, &ft, "Mike");
+    ok(r == ERROR_FUNCTION_FAILED, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiCloseHandle(hsuminfo);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    /* try again with a higher update count */
+    r = MsiGetSummaryInformation(hdb, NULL, 10, &hsuminfo);
+    ok(r == ERROR_SUCCESS, "MsiGetSummaryInformation failed\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_TITLE, VT_LPSTR, 0, NULL, "JungAh");
+    ok(r == ERROR_SUCCESS, "MsiSummaryInfoSetProperty failed\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_CODEPAGE, VT_LPSTR, 1, NULL, NULL);
+    ok(r == ERROR_DATATYPE_MISMATCH, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_CODEPAGE, VT_I2, 1, NULL, NULL);
+    ok(r == ERROR_SUCCESS, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_CODEPAGE, VT_I2, 1, &ft, "Mike");
+    ok(r == ERROR_SUCCESS, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoSetProperty(hsuminfo, PID_AUTHOR, VT_LPSTR, 1, &ft, "Mike");
+    ok(r == ERROR_SUCCESS, "MsiSummaryInfoSetProperty wrong error\n");
+
+    r = MsiSummaryInfoPersist(hsuminfo);
+    ok(r == ERROR_SUCCESS, "MsiSummaryInfoPersist failed\n");
+
+    MsiDatabaseCommit(hdb);
+
+    r = MsiCloseHandle(hsuminfo);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    r = MsiCloseHandle(hdb);
+    ok(r == ERROR_SUCCESS, "MsiCloseHandle failed\n");
+
+    r = DeleteFile(msifile);
+    ok(r, "DeleteFile failed\n");
+}
diff --git a/reactos/regtests/winetests/msi/testlist.c b/reactos/regtests/winetests/msi/testlist.c
new file mode 100644 (file)
index 0000000..eb38cdb
--- /dev/null
@@ -0,0 +1,25 @@
+#define WIN32_LEAN_AND_MEAN\r
+#include <windows.h>\r
+\r
+#define STANDALONE\r
+#include "wine/test.h"\r
+\r
+extern void func_db(void);\r
+extern void func_format(void);\r
+extern void func_install(void);\r
+extern void func_msi(void);\r
+extern void func_package(void);\r
+extern void func_record(void);\r
+extern void func_suminfo(void);\r
+\r
+const struct test winetest_testlist[] =\r
+{\r
+    { "db", func_db },\r
+    { "format", func_format },\r
+    { "install", func_install },\r
+    { "msi", func_msi },\r
+    { "package", func_package },\r
+    { "record", func_record },\r
+    { "suminfo", func_suminfo },\r
+    { 0, 0 }\r
+};\r