root/src/tests/kits/storage/MimeTypeTest.cpp
// MimeTypeTest.cpp

#include <ctype.h>                      // For tolower()
#include <fcntl.h>                      // open()
#include <map>
#include <queue>
#include <stdio.h>
#include <string.h>                     // For memcmp()
#include <string>
#include <unistd.h>
#include <vector>


#include <fs_attr.h>            // For struct attr_info
#include <fs_info.h>
#include <Application.h>
#include <Bitmap.h>
#include <DataIO.h>
#include <Drivers.h>            // B_GET_ICON, device_icon
#include <Message.h>
#include <Mime.h>
#if !TEST_R5
        #include <mime/database_support.h>
#endif
#include <Path.h>                       // Only needed for entry_ref dumps
#include <StorageKit.h>
#include <String.h>
#include <storage_support.h>    // for split_path()

#include "TestShell.h"
#include "TestApp.h"
#include "TestUtils.h"

#include "MimeTypeTest.h"

// MIME database directories
static const char *testDir                              = "/tmp/mimeTestDir";
static const char *R5DatabaseDir                = "/boot/home/config/settings/beos_mime";
#if TEST_R5
static std::string mimeDatabaseDir              = R5DatabaseDir;
#else
static std::string mimeDatabaseDir              = BPrivate::Storage::Mime::kDatabaseDir;
#endif

// MIME Test Types
// testType and testTypeApp are Delete()d after each test.
static const char *testType                             = "text/x-vnd.obos-Storage-Kit-Test";
static const char *testType1                    = "text/x-vnd.obos-Storage-Kit-Test1";
static const char *testType2                    = "text/x-vnd.obos-Storage-Kit-Test2";
static const char *testType3                    = "text/x-vnd.obos-Storage-Kit-Test3";
static const char *testType4                    = "text/x-vnd.obos-Storage-Kit-Test4";
static const char *testType5                    = "text/x-vnd.obos-Storage-Kit-Test5";
static const char *testTypeApp                  = "application/StorageKit-Test";
static const char *testTypeApp1                 =  "application/"
                                                                                   "x-vnd.obos.mime.test.test1";
static const char *testTypeApp2                 =  "application/"
                                                                                   "x-vnd.obos.mime.test.test2";
static const char *testTypeApp3                 =  "application/"
                                                                                   "x-vnd.obos.mime.test.test3";
static const char *testTypeInvalid              = "text/Are spaces valid?";
static const char *testTypeSuperValid   = "valid-but-fake-supertype";
static const char *testTypeSuperInvalid = "?????";

// Real MIME types
static const char *wildcardType                 = "application/octet-stream";
static const char *applicationSupertype = "application";

// Application Paths
static const char *testApp                              = "/boot/beos/apps/SoundRecorder";
static const char *testApp2                             = "/boot/beos/apps/CDPlayer";
static const char *fakeTestApp                  = "/__this_isn't_likely_to_exist__";

// BMessage field names
static const char *applicationsField            = "applications";
static const char *typeField                            = "type";
static const char *typesField                           = "types";
static const char *fileExtField                         = "extensions";
static const char *attrInfoField_Name           = "attr:name";
static const char *attrInfoField_PublicName     = "attr:public_name";
static const char *attrInfoField_Type           = "attr:type";
static const char *attrInfoField_Viewable       = "attr:viewable";
static const char *attrInfoField_Editable       = "attr:editable";

// Descriptions
static const char *testDescr                    = "Just a test, nothing more :-)";
static const char *testDescr2                   = "Another amazing test string";
static const char *longDescr                    =
"This description is longer than B_MIME_TYPE_LENGTH, which is quite useful for certain things... "
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
// Signatures
static const char *testSig                              = "application/x-vnd.obos.mime-type-test";
static const char *testSig2                             = "application/x-vnd.obos.mime-type-test-2";
static const char *longSig                              = "application/x-vnd.obos.mime-type-test-long."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";

// Declarations for handy dandy private functions
static bool operator==(BBitmap &bmp1, BBitmap &bmp2);
static bool operator!=(BBitmap &bmp1, BBitmap &bmp2);
static bool operator==(BMessage &msg1, BMessage &msg2);
static bool operator!=(BMessage &msg1, BMessage &msg2);
static void fill_bitmap(BBitmap &bmp, char value);
//static void dump_bitmap(BBitmap &bmp, char *name = "bmp");
#if !TEST_R5
        static status_t reduce_color_depth(BBitmap &src32, BBitmap &dest8);
#endif
//static void dump_ref(entry_ref *ref, char* name = "ref");
static void to_lower(const char *str, std::string &result);
static std::string to_lower(const char *str);
static void remove_type(const char *type, const char *databaseDir = mimeDatabaseDir.c_str());
static bool type_exists(const char *type, const char *databaseDir = mimeDatabaseDir.c_str());
class ContainerAdapter;
class SetAdapter;
class QueueAdapter;
void FillWithMimeTypes(ContainerAdapter &container, BMessage &typeMessage, const char* fieldName);
        // Used to add all the types in a BMessage from GetInstalled*Types() to some
        // sort of container object (via an adapter to support differing interfaces).

// Custom TestSuite class to allow us to make a copy of the MIME database directory
// before running all the BMimeType tests
class MimeTypeTestSuite : public CppUnit::TestSuite {
public:
        MimeTypeTestSuite() : CppUnit::TestSuite(), fMimeDirExisted(false) {}
        virtual void setUp()
        {
                // If we're using a directory other than the R5 MIME database directory, make
                // sure that directory exists. If not, make a complete copy of the R5 database
                // directory.
                if (mimeDatabaseDir != R5DatabaseDir) {
                        BEntry dir(mimeDatabaseDir.c_str());
                        if (dir.InitCheck() != B_OK || !dir.Exists()) {
                                if (BTestShell::GlobalBeVerbose())
                                        cout << "(Making a copy of your MIME database at '" + mimeDatabaseDir + "')" << endl;
                                std::string cmd = std::string("copyattr -d -r -- ") + R5DatabaseDir
                                        + " " + mimeDatabaseDir;
                                ExecCommand(cmd.c_str());
                        } else {
                                fMimeDirExisted = true;
                                if (BTestShell::GlobalBeVerbose())
                                        cout << "(Using existing copy of MIME database in '" + mimeDatabaseDir + "')" << endl;
                        }
                }
        }

        virtual void tearDown()
        {
                if (mimeDatabaseDir != R5DatabaseDir && !fMimeDirExisted) {
                        if (BTestShell::GlobalBeVerbose())
                                cout << "(Removing copy of MIME database in '" + mimeDatabaseDir + "')" << endl;
                        std::string cmd = std::string("rm -rf ") + mimeDatabaseDir;
                        ExecCommand(cmd.c_str());
                }
        }

        virtual void run( CppUnit::TestResult *result )
        {
                setUp();
                CppUnit::TestSuite::run(result);
                tearDown();
        }
private:
        bool fMimeDirExisted;
};

// Suite
CppUnit::Test*
MimeTypeTest::Suite() {
        MimeTypeTestSuite *suite = new MimeTypeTestSuite();
        typedef CppUnit::TestCaller<MimeTypeTest> TC;

        // Tyler
        suite->addTest( new TC("BMimeType::Install/Delete Test",
                                                   &MimeTypeTest::InstallDeleteTest) );
        suite->addTest( new TC("BMimeType::App Hint Test",
                                                   &MimeTypeTest::AppHintTest) );
        suite->addTest( new TC("BMimeType::Attribute Info Test",
                                                   &MimeTypeTest::AttrInfoTest) );
        suite->addTest( new TC("BMimeType::Long Description Test",
                                                   &MimeTypeTest::LongDescriptionTest) );
        suite->addTest( new TC("BMimeType::Short Description Test",
                                                   &MimeTypeTest::ShortDescriptionTest) );
        suite->addTest( new TC("BMimeType::File Extensions Test",
                                                   &MimeTypeTest::FileExtensionsTest) );
        suite->addTest( new TC("BMimeType::Icon Test (Large)",
                                                   &MimeTypeTest::LargeIconTest) );
        suite->addTest( new TC("BMimeType::Icon Test (Mini)",
                                                   &MimeTypeTest::MiniIconTest) );
        suite->addTest( new TC("BMimeType::Icon For Type Test (Large)",
                                                   &MimeTypeTest::LargeIconForTypeTest) );
        suite->addTest( new TC("BMimeType::Icon For Type Test (Mini)",
                                                   &MimeTypeTest::MiniIconForTypeTest) );
        suite->addTest( new TC("BMimeType::Installed Types Test",
                                                   &MimeTypeTest::InstalledTypesTest) );
        suite->addTest( new TC("BMimeType::Preferred App Test",
                                                   &MimeTypeTest::PreferredAppTest) );
        suite->addTest( new TC("BMimeType::Supporting Apps Test",
                                                   &MimeTypeTest::SupportingAppsTest) );
        suite->addTest( new TC("BMimeType::Supported Types Test",
                                                   &MimeTypeTest::SupportedTypesTest) );
        suite->addTest( new TC("BMimeType::Wildcard Apps Test",
                                                   &MimeTypeTest::WildcardAppsTest) );

        // Ingo
        suite->addTest( new TC("BMimeType::Initialization Test",
                                                   &MimeTypeTest::InitTest) );
        suite->addTest( new TC("BMimeType::MIME String Test",
                                                   &MimeTypeTest::StringTest) );
        suite->addTest( new TC("BMimeType::MIME Monitoring Test",
                                                   &MimeTypeTest::MonitoringTest) );
        suite->addTest( new TC("BMimeType::update_mime_info() Test",
                                                   &MimeTypeTest::UpdateMimeInfoTest) );
        suite->addTest( new TC("BMimeType::create_app_meta_mime() Test",
                                                   &MimeTypeTest::CreateAppMetaMimeTest) );
        suite->addTest( new TC("BMimeType::get_device_icon() Test",
                                                   &MimeTypeTest::GetDeviceIconTest) );
        suite->addTest( new TC("BMimeType::Sniffer Rule Test",
                                                   &MimeTypeTest::SnifferRuleTest) );
        suite->addTest( new TC("BMimeType::Sniffing Test",
                                                   &MimeTypeTest::SniffingTest) );


        return suite;
}

// Handy comparison operators for BBitmaps. The size and color depth
// are compared first, followed by the bitmap data.
bool
operator==(BBitmap &bmp1, BBitmap &bmp2) {
        if (bmp1.Bounds() == bmp2.Bounds()) {
//              printf("bmp== 1\n");
                if (bmp1.ColorSpace() == bmp2.ColorSpace()) {
//                      printf("bmp== 2\n");
                        char *data1 = (char*)bmp1.Bits();
                        char *data2 = (char*)bmp2.Bits();
                        // NOTE! It's possible that padding bits might not get copied verbatim,
                        // which could lead to unexpected failures. If things are acting weird,
                        // you might try the commented out code below (keeping in mind that it
                        // currently only works for 8-bit color depths).
                        for (int i = 0; i < bmp1.BitsLength(); data1++, data2++, i++) {
                                if (*data1 != *data2) {
//                                      printf("i == %d\n", i);
                                        return false;
                                }
                        }
/*                      for (int i = 0; i < bmp1.Bounds().IntegerHeight(); i++) {
                                for (int j = 0; j < bmp1.Bounds().IntegerWidth(); j++) {
//                                      printf("(%d, %d)", data1[(i * bmp1.BytesPerRow()) + j], data2[(i * bmp2.BytesPerRow()) + j]);
                                        if (data1[(i * bmp1.BytesPerRow()) + j] != data2[(i * bmp2.BytesPerRow()) + j]) {
                                                printf("fail at (%d, %d)\n", j, i);
                                                return false;
                                        }
                                }
                        }*/
                        return true;
                } else
                        return false;
        } else
                return false;
}

bool
operator!=(BBitmap &bmp1, BBitmap &bmp2) {
        return !(bmp1 == bmp2);
}

// Handy comparison operators for BMessages. The BMessages are checked field
// by field, each of which is verified to be identical with respect to: type,
// count, and data (all items).
bool
operator==(BMessage &msg1, BMessage &msg2) {
        status_t err = B_OK;

        // For now I'm ignoring the what fields...I shall deal with that later :-)
        if (msg1.what != msg2.what)
                return false;

/*
        printf("----------------------------------------------------------------------\n");
        msg1.PrintToStream();
        msg2.PrintToStream();
        printf("----------------------------------------------------------------------\n");
*/

        // Check the counts of field names
        int count1, count2;
        count1 = msg1.CountNames(B_ANY_TYPE);
        count2 = msg2.CountNames(B_ANY_TYPE);
        if (count1 != count2 && (count1 == 0 || count2 == 0))
                return false;

        // Iterate over all the names in msg1 and check that the field
        // with the same name exists in msg2, is of the same type, and
        // contains identical data.
        for (int i = 0; i < count1 && !err; i++) {
                char *name;
                type_code typeFound1, typeFound2;
                int32 countFound1, countFound2;

                // Check type and count info
                err = msg1.GetInfo(B_ANY_TYPE, i, &name, &typeFound1, &countFound1);
                if (!err)
                        err = msg2.GetInfo(name, &typeFound2, &countFound2);
                if (!err)
                        err = (typeFound1 == typeFound2 && countFound1 == countFound2 ? B_OK : B_ERROR);
                if (!err) {
                        // Check all the data items
                        for (int j = 0; j < countFound1; j++) {
                                void *data1, *data2;
                                ssize_t bytes1, bytes2;

                                err = msg1.FindData(name, typeFound1, j, (const void**)&data1, &bytes1);
                                if (!err)
                                        err = msg2.FindData(name, typeFound2, j, (const void**)&data2, &bytes2);
                                if (!err)
                                        err = (bytes1 == bytes2 && memcmp(data1, data2, bytes1) == 0 ? B_OK : B_ERROR);
                        }
                }
        }

        return !err;
}

bool
operator!=(BMessage &msg1, BMessage &msg2) {
        return !(msg1 == msg2);
}

// Fills the bitmap data with the given character
void
fill_bitmap(BBitmap &bmp, char value) {
        char *data = (char*)bmp.Bits();
        for (int i = 0; i < bmp.BitsLength(); data++, i++) {
//              printf("(%d -> ", *data);
                *data = value;
//              printf("%d)", *data);
        }
//      printf("\n");
}

// Fills the bitmap data with the given character
void
fill_bitmap32(BBitmap &bmp, char r, char g, char b, char a) {
        if (bmp.ColorSpace() == B_RGB32 || bmp.ColorSpace() == B_RGBA32) {
                char *data = (char*)bmp.Bits();
                for (int i = 0; i+3 < bmp.BitsLength(); data += 4, i+= 4) {
                        data[0] = b;
                        data[1] = g;
                        data[2] = r;
                        data[3] = a;
//                      printf("(%d,%d,%d,%d)", data[2], data[1], data[0], data[3]);
                }
//              printf("\n");
        }
}

// Dumps the size, colorspace, and first data byte
// of the bitmap to stdout
/*void
dump_bitmap(BBitmap &bmp, char *name = "bmp") {
        printf("%s == (%ldx%ld, ", name, bmp.Bounds().IntegerWidth()+1,
                bmp.Bounds().IntegerHeight()+1);
        switch (bmp.ColorSpace()) {
                case B_CMAP8:
                        printf("B_CMAP8");
                        break;

                case B_RGB32:
                        printf("B_RGB32");
                        break;

                case B_RGBA32:
                        printf("B_RGBA32");
                        break;

                default:
                        printf("%x", bmp.ColorSpace());
                        break;
        }
        printf(", %d, [", *(char*)bmp.Bits());
        char *data = (char*)bmp.Bits();
        for (int i = 0; i < bmp.BitsLength() && i < 20; data++, i++)
                printf("%d,", *data);
        printf("]\n");
}*/

// Uses BBitmap::SetBits() to convert the B_RGB32 bitmap in src32
// to a B_CMAP8 bitmap in dest8
#if !TEST_R5
status_t
reduce_color_depth(BBitmap &src32, BBitmap &dest8)
{
        status_t err = (src32.ColorSpace() == B_RGB32
                                      && dest8.ColorSpace() == B_CMAP8
                                        && src32.InitCheck() == B_OK
                                          && dest8.InitCheck() == B_OK
                                                && src32.Bounds() == dest8.Bounds())
                                                  ? B_OK
                                                    : B_BAD_VALUE;
        if (!err) {
                // Set each pixel individually, since SetBits() for B_RGB32 takes
                // 24-bit rgb pixel data...
                char *data = (char*)src32.Bits();
                for (int32 i = 0; i*4+3 < src32.BitsLength(); data += 4, i++) {
                        char rgb[3];
                        rgb[0] = data[2];       // red
                        rgb[1] = data[1];       // green
                        rgb[2] = data[0];       // blue
                        dest8.SetBits(rgb, 3, i, B_RGB32);
                }
        }
        return err;
}
#endif

// IconHelper and IconForTypeHelper:
// Adapter(?) classes needed to reuse icon tests among {Get,Set}Icon() and {Get,Set}IconForType()
// What originally were meant to encapsulate the variations among calls to the various BMimeType
// icon functions have now exploded into beefy helper classes with a bunch of BBitmap related
// functionality as well. A lot of this stuff doesn't necessarily belong

class IconHelper {
public:
        IconHelper(icon_size which)
                : bmp1(BitmapBounds(which), B_CMAP8),
                  bmp2(BitmapBounds(which), B_CMAP8),
                  bmpTemp(BitmapBounds(which), B_CMAP8),
                  size(which)
        {
                // Initialize our three bitmaps to different "colors"
                fill_bitmap(bmp1, 1);
                fill_bitmap(bmp2, 2);
                fill_bitmap(bmpTemp, 3);
        }

        virtual ~IconHelper() {}

        // Returns the proper bitmap bounds for the given icon size
        BRect BitmapBounds(icon_size isize) {
                return isize == B_LARGE_ICON ? BRect(0,0,31,31) : BRect(0,0,15,15);
        }

        // Returns the proper bitmap bounds for this helper's icon size
        BRect BitmapBounds() {
                return BitmapBounds(size);
        }

        // Used to call the appropriate GetIcon[ForType] function
        virtual status_t GetIcon(BMimeType &mime, BBitmap *icon) {
                return mime.GetIcon(icon, size);
        }

        // Used to call the appropriate SetIcon[ForType] function
        virtual status_t SetIcon(BMimeType &mime, BBitmap *icon) {
                return mime.SetIcon(icon, size);
        }

        // Used to call the appropriate DeleteIcon[ForType] function
        virtual status_t DeleteIcon(BMimeType &mime) {
#if TEST_R5
                return B_BAD_VALUE;
#else
                return mime.DeleteIcon(size);
#endif
        }

        BBitmap* TempBitmap() {
                return &bmpTemp;
        }

        BBitmap* Bitmap1() {
                return &bmp1;
        }

        BBitmap* Bitmap2() {
                return &bmp2;
        }

        icon_size Size() {
                return size;
        }

protected:
        BBitmap bmp1;
        BBitmap bmp2;
        BBitmap bmpTemp;
        icon_size size;
};

class IconForTypeHelper : public IconHelper {
public:
        IconForTypeHelper(const char *fileType, icon_size which)
                : IconHelper(which), fileType(fileType) {}
        virtual ~IconForTypeHelper() {}
        virtual status_t GetIcon(BMimeType &mime, BBitmap *icon) {
                return mime.GetIconForType(fileType.c_str(), icon, size);
        }
        virtual status_t SetIcon(BMimeType &mime, BBitmap *icon) {
                return mime.SetIconForType(fileType.c_str(), icon, size);
        }
        virtual status_t DeleteIcon(BMimeType &mime) {
#if TEST_R5
                return B_BAD_VALUE;
#else
                return mime.DeleteIconForType(fileType.c_str(), size);
#endif
        }
protected:
        std::string fileType;
};

// Adapter classes used by FillWithMimeTypes() to facilitate
// addition of strings to containers with varying interfaces
class ContainerAdapter {
public:
        virtual void Add(std::string value) = 0;
};

class SetAdapter : public ContainerAdapter {
public:
        SetAdapter(std::set<std::string> &set)
                : fSet(set) { }
        virtual void Add(std::string value) {
                fSet.insert(value);
        }
protected:
        std::set<std::string> &fSet;
};

class QueueAdapter : public ContainerAdapter {
public:
        QueueAdapter(std::queue<std::string> &queue)
                : fQueue(queue) { }
        virtual void Add(std::string value) {
                fQueue.push(value);
        }
protected:
        std::queue<std::string> &fQueue;
};


// setUp
void
MimeTypeTest::setUp()
{
        BasicTest::setUp();
        execCommand(string("mkdir ") + testDir);
/*      // Better not to play with fire, so we'll make a copy of the
        // local mime database which we'll use for certain Haiku tests
        execCommand(string("mkdir ") + testDir
                                + " ; copyattr -d -r -- " + mimeDatabaseDir + "/\* " + testDir
                                ); */
        // Setup our application
        fApplication = new BTestApp(testSig);
        if (fApplication->Init() != B_OK) {
                fprintf(stderr, "Failed to initialize application.\n");
                delete fApplication;
                fApplication = NULL;
        }

}

// tearDown
void
MimeTypeTest::tearDown()
{
        execCommand(string("rm -rf ") + testDir);

        // Uninistall our test type
        //! /todo Uncomment the following uninstall code when all is said and done.
/*      BMimeType mime(testType);
        status_t err = mime.InitCheck();
        if (!err && mime.IsInstalled())
                err = mime.Delete();
        if (err)
                fprintf(stderr, "Failed to unistall test type \"%s\"\n", testType); */
        // Terminate the Application
        if (fApplication) {
                fApplication->Terminate();
                delete fApplication;
                fApplication = NULL;
        }
        // remove the types we've added
        const char * const testTypes[] = {
                testType, testType1, testType2, testType3, testType4, testType5,
                testTypeApp, testTypeApp1, testTypeApp2, testTypeApp3,
        };
        for (uint32 i = 0; i < sizeof(testTypes) / sizeof(const char*); i++) {
                BMimeType type(testTypes[i]);
                type.Delete();
        }
        BasicTest::tearDown();
}

// entry_ref dumping function ; this may be removed at any time

/*void
dump_ref(entry_ref *ref, char* name = "ref") {
        if (ref) {
                BPath path(ref);
                status_t err = path.InitCheck();
                if (!err) {
                        printf("%s == '%s'", name, path.Path());
                } else
                        printf("%s == ERROR", name);
                printf(" == (%ld, %lld, '%s')\n", ref->device, ref->directory, ref->name);

        } else
                printf("%s == (NULL)\n", name);
}*/

// App Hint

void
MimeTypeTest::AppHintTest() {
        // init a couple of entry_refs to applications
        BEntry entry(testApp);
        entry_ref appRef;
        CHK(entry.InitCheck() == B_OK);
        CHK(entry.GetRef(&appRef) == B_OK);
        BEntry entry2(testApp2);
        entry_ref appRef2;
        CHK(entry2.InitCheck() == B_OK);
        CHK(entry2.GetRef(&appRef2) == B_OK);
        // Uninitialized
        NextSubTest();
        {
                BMimeType mime;
                entry_ref ref;
                CHK(mime.InitCheck() == B_NO_INIT);
                CHK(mime.GetAppHint(&ref) != B_OK);             // R5 == B_BAD_VALUE
                CHK(mime.SetAppHint(&ref) != B_OK);             // R5 == B_BAD_VALUE
        }
        // NULL params
        NextSubTest();
        {
                entry_ref ref;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
#if TEST_R5
                CHK(!mime.IsInstalled());
                CHK(mime.GetAppHint(NULL) != B_OK);             // R5 == B_BAD_VALUE
                CHK(!mime.IsInstalled());
                CHK(mime.SetAppHint(NULL) != B_OK);             // Installs, R5 == B_ENTRY_NOT_FOUND
                CHK(mime.IsInstalled());
                CHK(mime.GetAppHint(NULL) != B_OK);             // R5 == B_BAD_VALUE
                CHK(mime.SetAppHint(NULL) != B_OK);             // R5 == B_ENTRY_NOT_FOUND
#else
                CHK(!mime.IsInstalled());
                CHK(mime.GetAppHint(NULL) != B_OK);             // B_BAD_VALUE
                CHK(!mime.IsInstalled());
                CHK(mime.SetAppHint(NULL) != B_OK);             // B_ENTRY_NOT_FOUND
                CHK(!mime.IsInstalled());
                CHK(mime.SetAppHint(&appRef) == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetAppHint(&ref) == B_OK);
                CHK(ref == appRef);
                CHK(mime.SetAppHint(NULL) == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetAppHint(&ref) != B_OK);             // B_ENTRY_NOT_FOUND
#endif
        }
        // Delete test
        NextSubTest();
        {
#if !TEST_R5
                entry_ref ref;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.DeleteAppHint() != B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.SetAppHint(&appRef) == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetAppHint(&ref) == B_OK);
                CHK(ref == appRef);
                CHK(mime.DeleteAppHint() == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetAppHint(&ref) != B_OK);
#endif
        }
        // Non-installed type
        NextSubTest();
        {
                entry_ref ref;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.GetAppHint(&ref) != B_OK);             // R5 == B_ENTRY_NOT_FOUND
                CHK(!mime.IsInstalled());
                CHK(mime.SetAppHint(&appRef) == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetAppHint(&ref) == B_OK);
                CHK(ref == appRef);
        }
        // Installed Type
        NextSubTest();
        {
                entry_ref ref;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                // Get() with no apphint installed
                CHK(mime.GetAppHint(&ref) == B_ENTRY_NOT_FOUND);
                // Initial Set()/Get()
                CHK(mime.SetAppHint(&appRef) == B_OK);
                CHK(mime.GetAppHint(&ref) == B_OK);
                CHK(ref == appRef);
                // Followup Set()/Get()
                CHK(mime.SetAppHint(&appRef2) == B_OK);
                CHK(mime.GetAppHint(&ref) == B_OK);
                CHK(ref == appRef2);
                CHK(ref != appRef);
        }
        // Installed Type, invalid entry_ref
        NextSubTest();
        {
                entry_ref ref(-1, -1, NULL);
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.SetAppHint(&appRef) == B_OK);
                CHK(mime.SetAppHint(&ref) != B_OK);     // R5 == B_BAD_VALUE
        }
        // Installed Type, fake/invalid entry_ref
        NextSubTest();
        {
                entry_ref ref(0, 0, "__this_ought_not_exist__");
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.SetAppHint(&appRef) == B_OK);
                CHK(mime.SetAppHint(&ref) != B_OK);     // R5 == B_ENTRY_NOT_FOUND
        }
        // Installed Type, abstract entry_ref
        NextSubTest();
        {
                entry_ref fakeRef;
                entry_ref ref;
                BEntry entry(fakeTestApp);
                CHK(entry.InitCheck() == B_OK);
                CHK(!entry.Exists());
                CHK(entry.GetRef(&fakeRef) == B_OK);
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.SetAppHint(&appRef) == B_OK);
                CHK(mime.SetAppHint(&fakeRef) == B_OK);
                CHK(mime.GetAppHint(&ref) == B_OK);
                CHK(ref == fakeRef);
                CHK(ref != appRef);
        }
}

// Attr Info

void
MimeTypeTest::AttrInfoTest() {
        // Create some messages to sling around
        const int32 WHAT = 233; // This is the what value that GAI() returns...not sure if it has a name yet
        BMessage msg1(WHAT), msg2(WHAT), msg3(WHAT), msgIncomplete1(WHAT), msgIncomplete2(WHAT);

        CHK(msg1.AddString(attrInfoField_Name, "Color") == B_OK);
        CHK(msg1.AddString(attrInfoField_PublicName, "The Color") == B_OK);
        CHK(msg1.AddInt32(attrInfoField_Type, B_STRING_TYPE) == B_OK);
        CHK(msg1.AddBool(attrInfoField_Viewable, true) == B_OK);
        CHK(msg1.AddBool(attrInfoField_Editable, true) == B_OK);

        CHK(msg1.AddString(attrInfoField_Name, "High Score") == B_OK);
        CHK(msg1.AddString(attrInfoField_PublicName, "The Highest Score Ever") == B_OK);
        CHK(msg1.AddInt32(attrInfoField_Type, B_INT32_TYPE) == B_OK);
        CHK(msg1.AddBool(attrInfoField_Viewable, false) == B_OK);
        CHK(msg1.AddBool(attrInfoField_Editable, false) == B_OK);

        CHK(msg2.AddString(attrInfoField_Name, "Volume") == B_OK);
        CHK(msg2.AddString(attrInfoField_PublicName, "Loudness") == B_OK);
        CHK(msg2.AddInt32(attrInfoField_Type, B_DOUBLE_TYPE) == B_OK);
        CHK(msg2.AddBool(attrInfoField_Viewable, true) == B_OK);
        CHK(msg2.AddBool(attrInfoField_Editable, true) == B_OK);

        CHK(msg3.AddString(attrInfoField_Name, "Volume") == B_OK);
        CHK(msg3.AddString(attrInfoField_PublicName, "Loudness") == B_OK);
        CHK(msg3.AddInt32(attrInfoField_Type, B_DOUBLE_TYPE) == B_OK);
        CHK(msg3.AddBool(attrInfoField_Viewable, true) == B_OK);
        CHK(msg3.AddBool(attrInfoField_Editable, true) == B_OK);

        CHK(msgIncomplete1.AddString(attrInfoField_Name, "Color") == B_OK);
        CHK(msgIncomplete1.AddString(attrInfoField_PublicName, "The Color") == B_OK);
        CHK(msgIncomplete1.AddInt32(attrInfoField_Type, B_STRING_TYPE) == B_OK);
        CHK(msgIncomplete1.AddBool(attrInfoField_Viewable, true) == B_OK);
        CHK(msgIncomplete1.AddBool(attrInfoField_Editable, true) == B_OK);

        CHK(msgIncomplete1.AddString(attrInfoField_Name, "High Score") == B_OK);
//      CHK(msgIncomplete1.AddString(attrInfoField_PublicName, "The Highest Score Ever") == B_OK);
        CHK(msgIncomplete1.AddInt32(attrInfoField_Type, B_INT32_TYPE) == B_OK);
//      CHK(msgIncomplete1.AddBool(attrInfoField_Viewable, false) == B_OK);
        CHK(msgIncomplete1.AddBool(attrInfoField_Editable, false) == B_OK);

        CHK(msgIncomplete2.AddString(attrInfoField_Name, "Color") == B_OK);
//      CHK(msgIncomplete2.AddString(attrInfoField_PublicName, "The Color") == B_OK);
//      CHK(msgIncomplete2.AddInt32(attrInfoField_Type, B_STRING_TYPE) == B_OK);
//      CHK(msgIncomplete2.AddBool(attrInfoField_Viewable, true) == B_OK);
        CHK(msgIncomplete2.AddBool(attrInfoField_Editable, true) == B_OK);

        CHK(msg1 == msg1);
        CHK(msg2 == msg2);
        CHK(msg3 == msg3);
        CHK(msg1 != msg2);
        CHK(msg1 != msg3);
        CHK(msg2 == msg3);

        // Uninitialized
        NextSubTest();
        {
                BMimeType mime;
                BMessage msg;

                CHK(mime.InitCheck() == B_NO_INIT);
                CHK(mime.GetAttrInfo(&msg) != B_OK);            // R5 == B_BAD_VALUE
                CHK(mime.SetAttrInfo(&msg) != B_OK);            // R5 == B_BAD_VALUE
        }

        // NULL params
        NextSubTest();
        {
#if !TEST_R5
                BMessage msg;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.DeleteAttrInfo() != B_OK);
                CHK(!mime.IsInstalled());
                msg1.RemoveName(typeField);                                             // Clear "type" field, since SAI() just adds another
                CHK(mime.SetAttrInfo(&msg1) == B_OK);
                CHK(mime.IsInstalled());
                CHK(msg != msg1);
                CHK(mime.GetAttrInfo(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" field as GAI() does
                CHK(msg == msg1);
                CHK(mime.SetAttrInfo(NULL) == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetAttrInfo(&msg) != B_OK);
#endif
        }
        // Delete test
        NextSubTest();
        {
#if !TEST_R5
                BMessage msg;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.DeleteAttrInfo() != B_OK);
                CHK(!mime.IsInstalled());
                msg1.RemoveName(typeField);                                             // Clear "type" field, since SAI() just adds another
                CHK(mime.SetAttrInfo(&msg1) == B_OK);
                CHK(mime.IsInstalled());
                CHK(msg != msg1);
                CHK(mime.GetAttrInfo(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" field as GAI() does
                CHK(msg == msg1);
                CHK(mime.DeleteAttrInfo() == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetAttrInfo(&msg) != B_OK);
#endif
        }

        // Improperly formatted BMessages
        NextSubTest();
        {
                BMessage msg(WHAT);
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);

                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());

                // Initial Set()/Get()
                msgIncomplete1.RemoveName(typeField);           // Clear "type" fields, since SAI() just adds another
                msgIncomplete2.RemoveName(typeField);
                CHK(msg != msgIncomplete1);
                CHK(msg != msgIncomplete2);
                CHK(mime.SetAttrInfo(&msgIncomplete1) == B_OK);
                CHK(mime.GetAttrInfo(&msg) == B_OK);
                CHK(msgIncomplete1.AddString(typeField, testType) == B_OK);     // Add in "type" fields as GFE() does
                CHK(msgIncomplete2.AddString(typeField, testType) == B_OK);
                CHK(msg == msgIncomplete1);
                CHK(msg != msgIncomplete2);
        }

        // Set() with improperly formatted message
        NextSubTest();
        {
                BMessage msg(WHAT);
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);

                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());

                // Initial Set()/Get()
                msgIncomplete1.RemoveName(typeField);           // Clear "type" fields, since SAI() just adds another
                msgIncomplete2.RemoveName(typeField);
                CHK(msg != msgIncomplete1);
                CHK(msg != msgIncomplete2);
                CHK(mime.SetAttrInfo(&msgIncomplete1) == B_OK);
                CHK(mime.GetAttrInfo(&msg) == B_OK);
                CHK(msgIncomplete1.AddString(typeField, testType) == B_OK);     // Add in "type" fields as GFE() does
                CHK(msgIncomplete2.AddString(typeField, testType) == B_OK);
                CHK(msg == msgIncomplete1);
                CHK(msg != msgIncomplete2);
        }

        // Set() with empty message
        NextSubTest();
        {
                BMimeType mime(testType);
                BMessage msgEmpty(WHAT);
                BMessage msg(WHAT);
                CHK(msg.AddInt32("stuff", 1234) == B_OK);       // Add an extra attribute to give us something to compare with

                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());

                // Set(empty)
                CHK(msg != msgEmpty);
                CHK(mime.SetAttrInfo(&msgEmpty) == B_OK);
                CHK(mime.GetAttrInfo(&msg) == B_OK);
                CHK(msgEmpty.AddString(typeField, testType) == B_OK);   // Add in "type" fields as GFE() does
                CHK(msg == msgEmpty);
        }

        // Set() with extra attributes in message
        NextSubTest();
        {
                BMimeType mime(testType);
                BMessage msg(WHAT);
                BMessage msgExtraSet(msg1);
                CHK(msgExtraSet.AddString("extra", ".extra") == B_OK);
                CHK(msgExtraSet.AddInt32("more_extras", 123) == B_OK);
                CHK(msgExtraSet.AddInt32("more_extras", 456) == B_OK);
                CHK(msgExtraSet.AddInt32("more_extras", 789) == B_OK);
                BMessage msgExtraGet(msgExtraSet);

                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());

                // Set(extra)/Get(empty)
                msg1.RemoveName(typeField);             // Clear "type" fields, since SFE() just adds another
                msg2.RemoveName(typeField);
                CHK(msg != msg1);
                CHK(msg != msgExtraSet);
                CHK(mime.SetAttrInfo(&msgExtraSet) == B_OK);
                CHK(mime.GetAttrInfo(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" fields as GFE() does
                CHK(msgExtraSet.AddString(typeField, testType) == B_OK);
                CHK(msg == msgExtraSet);
                CHK(msg != msg1);

                // Get(extra)
                NextSubTest();
                CHK(mime.GetAttrInfo(&msgExtraGet) == B_OK);
                CHK(msgExtraGet == msgExtraSet);
                CHK(msgExtraGet != msg1);

                // Get(extra and then some)
                NextSubTest();
                CHK(msgExtraGet.AddInt32("more_extras", 101112) == B_OK);
                msgExtraGet.RemoveName(typeField);              // Clear "type" fields to be fair, since SFE() just adds another
                CHK(mime.GetAttrInfo(&msgExtraGet) == B_OK);    // Reinitializes result (clearing extra fields)
                CHK(msgExtraGet == msgExtraSet);
                CHK(msgExtraGet != msg1);

        }
        // Normal Function (Non-installed type)
        NextSubTest();
        {
                BMimeType mime(testType);
                BMessage msg(WHAT);
                BMessage msg2(WHAT);

                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);

                CHK(!mime.IsInstalled());
                CHK(mime.GetAttrInfo(&msg) != B_OK);            // R5 == B_ENTRY_NOT_FOUND
                CHK(!mime.IsInstalled());
                CHK(mime.SetAttrInfo(&msg) == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetAttrInfo(&msg2) == B_OK);
                CHK(msg.AddString(typeField, testType) == B_OK);        // Add in "type" fields as GAI() does
                CHK(msg == msg2);
        }

        // Normal Function
        NextSubTest();
        {
                BMessage msg(WHAT);
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);

                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());

                // Initial Set()/Get()
                msg1.RemoveName(typeField);             // Clear "type" fields, since SAI() just adds another
                msg2.RemoveName(typeField);
                CHK(msg != msg1);
                CHK(msg != msg2);
                CHK(mime.SetAttrInfo(&msg1) == B_OK);
                CHK(mime.GetAttrInfo(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" fields as GFE() does
                CHK(msg2.AddString(typeField, testType) == B_OK);
                CHK(msg == msg1);
                CHK(msg != msg2);

                // Followup Set()/Get()
                NextSubTest();
                CHK(msg.MakeEmpty() == B_OK);
                msg1.RemoveName(typeField);             // Clear "type" fields, since SFE() just adds another
                msg2.RemoveName(typeField);
                CHK(msg != msg1);
                CHK(msg != msg2);
                CHK(mime.SetAttrInfo(&msg2) == B_OK);
                CHK(mime.GetAttrInfo(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" fields as GFE() does
                CHK(msg2.AddString(typeField, testType) == B_OK);
                CHK(msg != msg1);
                CHK(msg == msg2);

                // Clear
                NextSubTest();
                CHK(msg.MakeEmpty() == B_OK);
                msg1.RemoveName(typeField);             // Clear "type" fields, since SFE() just adds another
                msg2.RemoveName(typeField);
                CHK(msg != msg1);
                CHK(msg != msg2);
#if !TEST_R5
                CHK(mime.SetAttrInfo(NULL) == B_OK);            // R5 == CRASH! despite what one might think should happen
                CHK(mime.GetAttrInfo(&msg) != B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" fields as GFE() does
                CHK(msg2.AddString(typeField, testType) == B_OK);
                CHK(msg != msg1);
                CHK(msg != msg2);
#endif
        }
}

// File Extensions

void
MimeTypeTest::FileExtensionsTest() {
        // Create some messages to sling around
        const int32 WHAT = 234; // This is the what value that GFE returns...not sure if it has a name yet
        BMessage msg1(WHAT), msg2(WHAT), msg3(WHAT);

        CHK(msg1.AddString(fileExtField, ".data") == B_OK);
        CHK(msg1.AddString(fileExtField, ".txt") == B_OK);
        CHK(msg1.AddString(fileExtField, ".png") == B_OK);
        CHK(msg1.AddString(fileExtField, ".html") == B_OK);

        CHK(msg2.AddString(fileExtField, ".data") == B_OK);
        CHK(msg2.AddString(fileExtField, ".txt") == B_OK);

        CHK(msg3.AddString(fileExtField, ".data") == B_OK);
        CHK(msg3.AddString(fileExtField, ".txt") == B_OK);

        CHK(msg1 == msg1);
        CHK(msg2 == msg2);
        CHK(msg3 == msg3);
        CHK(msg1 != msg2);
        CHK(msg1 != msg3);
        CHK(msg2 == msg3);

        // Uninitialized
        NextSubTest();
        {
                BMessage msg(WHAT);
                BMimeType mime;

                CHK(mime.InitCheck() == B_NO_INIT);
                CHK(mime.GetFileExtensions(&msg) != B_OK);      // R5 == B_BAD_VALUE
                CHK(mime.SetFileExtensions(&msg) != B_OK);      // R5 == B_BAD_VALUE
        }
        // NULL params
        NextSubTest();
        {
#if !TEST_R5
                BMessage msg;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.DeleteFileExtensions() != B_OK);
                CHK(!mime.IsInstalled());
                msg1.RemoveName(typeField);                                             // Clear "type" field, since SAI() just adds another
                CHK(mime.SetFileExtensions(&msg1) == B_OK);
                CHK(mime.IsInstalled());
                CHK(msg != msg1);
                CHK(mime.GetFileExtensions(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" field as GAI() does
                CHK(msg == msg1);
                CHK(mime.SetFileExtensions(NULL) == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetFileExtensions(&msg) != B_OK);
#endif
        }
        // Delete test
        NextSubTest();
        {
#if !TEST_R5
                BMessage msg;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.DeleteFileExtensions() != B_OK);
                CHK(!mime.IsInstalled());
                msg1.RemoveName(typeField);                                             // Clear "type" field, since SAI() just adds another
                CHK(mime.SetFileExtensions(&msg1) == B_OK);
                CHK(mime.IsInstalled());
                CHK(msg != msg1);
                CHK(mime.GetFileExtensions(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" field as GAI() does
                CHK(msg == msg1);
                CHK(mime.DeleteFileExtensions() == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetFileExtensions(&msg) != B_OK);
#endif
        }
        // Set() with empty message
        NextSubTest();
        {
                BMimeType mime(testType);
                BMessage msgEmpty(WHAT);
                BMessage msg(WHAT);
                CHK(msg.AddInt32("stuff", 1234) == B_OK);       // Add an extra attribute to give us something to compare with

                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());

                // Set(empty)
                CHK(msg != msgEmpty);
                CHK(mime.SetFileExtensions(&msgEmpty) == B_OK);
                CHK(mime.GetFileExtensions(&msg) == B_OK);
                CHK(msgEmpty.AddString(typeField, testType) == B_OK);   // Add in "type" fields as GFE() does
                CHK(msg == msgEmpty);
        }
        // Set() with extra attributes in message
        NextSubTest();
        {
                BMimeType mime(testType);
                BMessage msg(WHAT);
                BMessage msgExtraSet(msg1);
                CHK(msgExtraSet.AddString("extra", ".extra") == B_OK);
                CHK(msgExtraSet.AddInt32("more_extras", 123) == B_OK);
                CHK(msgExtraSet.AddInt32("more_extras", 456) == B_OK);
                CHK(msgExtraSet.AddInt32("more_extras", 789) == B_OK);
                BMessage msgExtraGet(msgExtraSet);

                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());

                // Set(extra)/Get(empty)
                msg1.RemoveName(typeField);             // Clear "type" fields, since SFE() just adds another
                msg2.RemoveName(typeField);
                CHK(msg != msg1);
                CHK(msg != msgExtraSet);
                CHK(mime.SetFileExtensions(&msgExtraSet) == B_OK);
                CHK(mime.GetFileExtensions(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" fields as GFE() does
                CHK(msgExtraSet.AddString(typeField, testType) == B_OK);
                CHK(msg == msgExtraSet);
                CHK(msg != msg1);

                // Get(extra)
                NextSubTest();
                CHK(mime.GetFileExtensions(&msgExtraGet) == B_OK);
                CHK(msgExtraGet == msgExtraSet);
                CHK(msgExtraGet != msg1);

                // Get(extra and then some)
                NextSubTest();
                CHK(msgExtraGet.AddInt32("more_extras", 101112) == B_OK);
                msgExtraGet.RemoveName(typeField);              // Clear "type" fields to be fair, since SFE() just adds another
                CHK(mime.GetFileExtensions(&msgExtraGet) == B_OK);      // Reinitializes result (clearing extra fields)
                CHK(msgExtraGet == msgExtraSet);
                CHK(msgExtraGet != msg1);

        }
        // Normal function
        NextSubTest();
        {
                BMessage msg(WHAT);
                BMimeType mime(testType);

                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());

                // Initial Set()/Get()
                msg1.RemoveName(typeField);             // Clear "type" fields, since SFE() just adds another
                msg2.RemoveName(typeField);
                CHK(msg != msg1);
                CHK(msg != msg2);
                CHK(mime.SetFileExtensions(&msg1) == B_OK);
                CHK(mime.GetFileExtensions(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" fields as GFE() does
                CHK(msg2.AddString(typeField, testType) == B_OK);
                CHK(msg == msg1);
                CHK(msg != msg2);

                // Followup Set()/Get()
                NextSubTest();
                CHK(msg.MakeEmpty() == B_OK);
                msg1.RemoveName(typeField);             // Clear "type" fields, since SFE() just adds another
                msg2.RemoveName(typeField);
                CHK(msg != msg1);
                CHK(msg != msg2);
                CHK(mime.SetFileExtensions(&msg2) == B_OK);
                CHK(mime.GetFileExtensions(&msg) == B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" fields as GFE() does
                CHK(msg2.AddString(typeField, testType) == B_OK);
                CHK(msg != msg1);
                CHK(msg == msg2);

                // Clear
                NextSubTest();
                CHK(msg.MakeEmpty() == B_OK);
                msg1.RemoveName(typeField);             // Clear "type" fields, since SFE() just adds another
                msg2.RemoveName(typeField);
                CHK(msg != msg1);
                CHK(msg != msg2);
#if !TEST_R5
                CHK(mime.SetFileExtensions(NULL) == B_OK);              // R5 == CRASH! despite what the BeBook says
                CHK(mime.GetFileExtensions(&msg) != B_OK);
                CHK(msg1.AddString(typeField, testType) == B_OK);       // Add in "type" fields as GFE() does
                CHK(msg2.AddString(typeField, testType) == B_OK);
                CHK(msg != msg1);
                CHK(msg != msg2);
#endif
        }
}


// Icon Test Helper Function

void
MimeTypeTest::IconTest(IconHelper &helper) {
        BBitmap *bmp = helper.TempBitmap();
        // Unitialized
        NextSubTest();
        {
                BMimeType mime;
                CHK(mime.InitCheck() == B_NO_INIT);
                CHK(helper.GetIcon(mime, bmp) != B_OK); // R5 == B_BAD_VALUE
                CHK(helper.SetIcon(mime, bmp) != B_OK); // R5 == B_BAD_VALUE
        }
        // Non-installed type
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                // Test
                CHK(helper.GetIcon(mime, bmp) != B_OK); // R5 == B_ENTRY_NOT_FOUND
                CHK(!mime.IsInstalled());
                CHK(helper.SetIcon(mime, helper.Bitmap1()) == B_OK);
                CHK(mime.IsInstalled());
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
        }
        // NULL params
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                // Uninstalled
                CHK(helper.GetIcon(mime, NULL) != B_OK);        // B_BAD_VALUE
                CHK(!mime.IsInstalled());
                CHK(helper.SetIcon(mime, NULL) != B_OK);        // R5 == Installs, B_ENTRY_NOT_FOUND
                                                                                                        // Haiku == Doesn't install, B_ENTRY_NOT_FOUND
#if TEST_R5
                CHK(mime.IsInstalled());
#else
                CHK(!mime.IsInstalled());
                CHK(mime.Install() == B_OK);
#endif
                CHK(helper.GetIcon(mime, bmp) != B_OK); // B_ENTRY_NOT_FOUND
                // Installed
                CHK(helper.GetIcon(mime, NULL) != B_OK);        // B_BAD_VALUE
                CHK(helper.SetIcon(mime, helper.Bitmap1()) == B_OK);
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
                CHK(helper.SetIcon(mime, NULL) == B_OK);
                CHK(helper.GetIcon(mime, bmp) != B_OK); // B_ENTRY_NOT_FOUND
        }
        // Delete test
        NextSubTest();
        {
#if !TEST_R5
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(helper.DeleteIcon(mime) != B_OK);
                CHK(!mime.IsInstalled());
                CHK(helper.SetIcon(mime, helper.Bitmap2()) == B_OK);
                CHK(mime.IsInstalled());
                fill_bitmap(*bmp, 100);
                CHK(*bmp != *helper.Bitmap2());
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap2());
                CHK(helper.DeleteIcon(mime) == B_OK);
                CHK(mime.IsInstalled());
                CHK(helper.GetIcon(mime, bmp) != B_OK);
#endif
        }
        // Invalid Bitmap Size (small -- 10x10)
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                // Init Test Bitmap
                BBitmap testBmp(BRect(0,0,9,9), B_CMAP8);
                fill_bitmap(testBmp, 3);
                // Test Set()
                CHK(testBmp != *helper.Bitmap1());
                CHK(helper.SetIcon(mime, helper.Bitmap1()) == B_OK);
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
                CHK(helper.SetIcon(mime, &testBmp) != B_OK);    // R5 == B_BAD_VALUE
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
                CHK(*bmp != testBmp);
                // Test Get()
                fill_bitmap(testBmp, 3);
                CHK(helper.SetIcon(mime, helper.Bitmap1()) == B_OK);
                CHK(helper.GetIcon(mime, &testBmp) != B_OK);    // R5 == B_BAD_VALUE
        }
        // Invalid Bitmap Size (large -- 100x100)
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                // Init Test Bitmap
                BBitmap testBmp(BRect(0,0,99,99), B_CMAP8);
                // Test Set()
                fill_bitmap(testBmp, 3);
                CHK(testBmp != *helper.Bitmap1());
                CHK(helper.SetIcon(mime, helper.Bitmap1()) == B_OK);
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
                CHK(helper.SetIcon(mime, &testBmp) != B_OK);    // R5 == B_BAD_VALUE
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
                CHK(*bmp != testBmp);
                // Test Get()
                fill_bitmap(testBmp, 3);
                CHK(helper.SetIcon(mime, helper.Bitmap1()) == B_OK);
                CHK(helper.GetIcon(mime, &testBmp) != B_OK);    // R5 == B_BAD_VALUE
        }
        // Non-B_CMAP8 Color Depth (not really supported under R5)
        NextSubTest();
        {
#if !TEST_R5
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                // Init Test Bitmap
                BBitmap testBmp(helper.BitmapBounds(), B_RGB32);
                // Test Set()
//              fill_bitmap(testBmp, 4);
                fill_bitmap32(testBmp, 10, 20, 30, 40);                         // Fill our 32-bit bitmap
                BBitmap testBmp8(helper.BitmapBounds(), B_CMAP8);       // Create an 8-bit bitmap the same size
//              dump_bitmap(testBmp8);
                reduce_color_depth(testBmp, testBmp8);                          // Make it an 8-bit version of the 32-bit bitmap
//              dump_bitmap(testBmp8);
                CHK(testBmp != *helper.Bitmap1());
                CHK(helper.SetIcon(mime, helper.Bitmap1()) == B_OK);
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
                CHK(helper.SetIcon(mime, &testBmp) == B_OK);
                        // R5 == B_OK, despite being invalid color depth; however, icon is not actually set,
                        // and any subsequent call to GetIcon() will cause the application to crash...
                CHK(helper.GetIcon(mime, bmp) == B_OK); // R5 == CRASH!, Haiku == Damn right I can handle that shit
                CHK(*bmp != *helper.Bitmap1());
                CHK(*bmp != testBmp);                           // Shouldn't match, since SetIcon() reduces to B_CMAP8
                CHK(*bmp == testBmp8);                          // *Should* match, since it's the result of a similar reduction
#endif
        }
        // Normal Function
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                // Set() then Get()
                fill_bitmap(*bmp, 3);
                CHK(*bmp != *helper.Bitmap1());
                CHK(helper.SetIcon(mime, helper.Bitmap1()) == B_OK);
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
                // Set() then Get() again
                fill_bitmap(*bmp, 3);
                CHK(helper.SetIcon(mime, helper.Bitmap2()) == B_OK);
                CHK(helper.GetIcon(mime, bmp) == B_OK);
                CHK(*bmp == *helper.Bitmap2());
                CHK(*bmp != *helper.Bitmap1());
        }
}

// Icon For Type Helper Functions

void
MimeTypeTest::IconForTypeTest(IconForTypeHelper &helper) {
        IconTest(helper);       // First run all the icon tests
                // Then do some IconForType() specific tests

        BBitmap *bmp = helper.TempBitmap();

        // Invalid MIME string
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                // Set() then Get()
                fill_bitmap(*bmp, 3);
                CHK(*bmp != *helper.Bitmap1());
                CHK(mime.SetIconForType(testTypeInvalid, helper.Bitmap1(), helper.Size()) != B_OK);     // R5 == B_BAD_VALUE
                CHK(mime.GetIconForType(testTypeInvalid, bmp, helper.Size()) != B_OK);                          // R5 == B_BAD_VALUE
                CHK(*bmp != *helper.Bitmap1());
        }
        // NULL MIME string (just like calling respective {Get,Set}Icon() function)
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                // Set() then Get()
                fill_bitmap(*bmp, 3);
                CHK(*bmp != *helper.Bitmap1());
                CHK(mime.SetIconForType(NULL, helper.Bitmap1(), helper.Size()) == B_OK);
                CHK(mime.GetIconForType(NULL, bmp, helper.Size()) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
                // Verify GetIcon() does the same thing
                fill_bitmap(*bmp, 3);
                CHK(*bmp != *helper.Bitmap1());
                CHK(mime.GetIcon(bmp, helper.Size()) == B_OK);
                CHK(*bmp == *helper.Bitmap1());
                // Delete with dual NULL calls
                CHK(mime.SetIconForType(NULL, NULL, helper.Size()) == B_OK);
                CHK(mime.GetIconForType(NULL, bmp, helper.Size()) != B_OK);     // B_ENTRY_NOT_FOUND
                CHK(mime.GetIcon(bmp, helper.Size()) != B_OK);                          // B_ENTRY_NOT_FOUND
        }
}

void
MimeTypeTest::LargeIconTest() {
        IconHelper helper(B_LARGE_ICON);
        IconTest(helper);
}

void
MimeTypeTest::MiniIconTest() {
        IconHelper helper(B_MINI_ICON);
        IconTest(helper);
}

void
MimeTypeTest::LargeIconForTypeTest() {
        IconForTypeHelper helper(testType, B_LARGE_ICON);
        IconForTypeTest(helper);
}

void
MimeTypeTest::MiniIconForTypeTest() {
        IconForTypeHelper helper(testType, B_MINI_ICON);
        IconForTypeTest(helper);
}

bool isMIMESupertype(const char *type) {
        BMimeType sub, super;
        status_t err;
        err = !type || !BMimeType::IsValid(type);

        // See if the type is the same as it's supertype
        if (!err)
                err = sub.SetTo(type);
        if (!err)
                return sub.GetSupertype(&super) == B_BAD_VALUE;
                        // This is what R5::GetSupertype() returns when called on a supertype;

        return false;
}

void
MimeTypeTest::VerifyInstalledTypes() {
        // Check GetInstalledTypes(1)
        NextSubTest();
        {
                BMessage msg;

                // Get the list of installed types
                CHK(BMimeType::GetInstalledTypes(&msg) == B_OK);

                // Add all the type strings to a std::set
                std::set<std::string> typeSet;
                SetAdapter typeAdapter(typeSet);
                FillWithMimeTypes(typeAdapter, msg, "types");

                // Manually verify that the set of types returned by GetInstalledTypes()
                // and the types present in the database are exactly the same (ignoring
                // any files with names made of invalid characters, in case some bozo
                // manually added such a file :-)
                BDirectory rootDir(mimeDatabaseDir.c_str());
                BEntry superEntry;
                CHK(rootDir.InitCheck() == B_OK);
                rootDir.Rewind();
                while (true) {
                        status_t err = rootDir.GetNextEntry(&superEntry);
                        if (err == B_ENTRY_NOT_FOUND)
                                break;  // End of directory listing

                        CHK(!err);      // Any other error is unacceptable :-)

                        // Get the leaf name
                        char superLeafMixed[B_PATH_NAME_LENGTH+1];
                        CHK(superEntry.GetName(superLeafMixed) == B_OK);
                        std::string superLeaf;
                        to_lower(superLeafMixed, superLeaf);

                        // We're only interested in directories, as they map to
                        // supertypes (and since they map thusly, they must also
                        // be valid MIME strings)
                        if (superEntry.IsDirectory() && BMimeType::IsValid(superLeaf.c_str())) {
                                // First, find and remove the supertype from our set
                                CHK(typeSet.find(superLeaf.c_str()) != typeSet.end());
                                typeSet.erase(superLeaf.c_str());

                                // Second, iterate through all the entries in the directory.
                                // If the entry designates a valid MIME string, find it
                                // in the set and remove it.
                                BDirectory superDir(&superEntry);
                                BEntry subEntry;
                                CHK(superDir.InitCheck() == B_OK);
                                superDir.Rewind();
                                while (true) {
                                        status_t err = superDir.GetNextEntry(&subEntry);
                                        if (err == B_ENTRY_NOT_FOUND)
                                                break;  // End of directory listing

                                        CHK(!err);      // Any other error is unacceptable :-)

                                        // Get the leaf name
                                        char subLeafMixed[B_PATH_NAME_LENGTH+1];
                                        CHK(subEntry.GetName(subLeafMixed) == B_OK);
                                        std::string subLeaf;
                                        to_lower(subLeafMixed, subLeaf);

                                        // Verify it's a valid mime string. If so, find and remove from our set
                                        std::string subType = superLeaf + "/" + subLeaf;
                                        if (BMimeType::IsValid(subType.c_str())) {
                                                if (typeSet.find(subType.c_str()) == typeSet.end())
                                                        cout << "Fuckup == '" << subType << "'" << endl;
                                                CHK(typeSet.find(subType.c_str()) != typeSet.end());
                                                typeSet.erase(subType.c_str());
                                        }
                                }
                        }
                }

                // At this point our set should be empty :-) If it's not, you might check
                // that you haven't added any superfluous files to your MIME database (like
                // a __mime_table backup, for instance).
                CHK(typeSet.size() == 0);
        }
        NextSubTest();
        // Check GetInstalledTypes(2) and GetInstalledSupertypes()
        {
                BMessage msg;

                // Get the list of installed types
                CHK(BMimeType::GetInstalledSupertypes(&msg) == B_OK);
//              msg.PrintToStream();

                // Add all the type strings to a std::set
                std::set<std::string> typeSet;
                SetAdapter typeAdapter(typeSet);
                FillWithMimeTypes(typeAdapter, msg, "super_types");

                // Manually verify that the set of types returned by GetInstalledSupertypes()
                // and the types present in the database are exactly the same (ignoring
                // any files with names made of invalid characters, in case some bozo
                // manually added such a file :-)
                BDirectory rootDir(mimeDatabaseDir.c_str());
                BEntry superEntry;
                CHK(rootDir.InitCheck() == B_OK);
                rootDir.Rewind();
                while (true) {
                        status_t err = rootDir.GetNextEntry(&superEntry);
                        if (err == B_ENTRY_NOT_FOUND)
                                break;  // End of directory listing

                        CHK(!err);      // Any other error is unacceptable :-)

                        // Get the leaf name
                        char superLeafMixed[B_PATH_NAME_LENGTH+1];
                        CHK(superEntry.GetName(superLeafMixed) == B_OK);
                        std::string superLeaf;
                        to_lower(superLeafMixed, superLeaf);

                        // We're only interested in directories, as they map to
                        // supertypes (and since they map thusly, they must also
                        // be valid MIME strings)
                        if (superEntry.IsDirectory() && BMimeType::IsValid(superLeaf.c_str())) {
                                // First, find and remove the supertype from our set
                                CHK(typeSet.find(superLeaf.c_str()) != typeSet.end());
                                typeSet.erase(superLeaf.c_str());

                                // Second, get the list of corresponding subtypes and add them
                                // to a std::set to be used for verification
                                BMessage msg;
                                CHK(BMimeType::GetInstalledTypes(superLeaf.c_str(), &msg) == B_OK);
//                              msg.PrintToStream();

                                std::set<std::string> subtypeSet;
                                SetAdapter subtypeAdapter(subtypeSet);
                                FillWithMimeTypes(subtypeAdapter, msg, "types");

                                // Third, iterate through all the entries in the directory.
                                // If the entry designates a valid MIME string, find it
                                // in the subtype set and remove it.
                                BDirectory superDir(&superEntry);
                                BEntry subEntry;
                                CHK(superDir.InitCheck() == B_OK);
                                superDir.Rewind();
                                while (true) {
                                        status_t err = superDir.GetNextEntry(&subEntry);
                                        if (err == B_ENTRY_NOT_FOUND)
                                                break;  // End of directory listing

                                        CHK(!err);      // Any other error is unacceptable :-)

                                        // Get the leaf name
                                        char subLeafMixed[B_PATH_NAME_LENGTH+1];
                                        CHK(subEntry.GetName(subLeafMixed) == B_OK);
                                        std::string subLeaf;
                                        to_lower(subLeafMixed, subLeaf);

                                        // Verify it's a valid mime string. If so, find and remove from our set
                                        std::string subType = superLeaf + "/" + subLeaf;
                                        if (BMimeType::IsValid(subType.c_str())) {
                                                CHK(subtypeSet.find(subType.c_str()) != subtypeSet.end());
                                                subtypeSet.erase(subType.c_str());
                                        }
                                }

                                // At this point our subtype set should be empty :-)
                                CHK(subtypeSet.size() == 0);

                        }
                }

                // At this point our set should be empty :-)
                CHK(typeSet.size() == 0);
        }

}

void
MimeTypeTest::InstalledTypesTest() {
        // NULL params
        {
                BMessage msg;
                NextSubTest();

#if !TEST_R5
                CHK(BMimeType::GetInstalledTypes(NULL) != B_OK);                        // R5 == CRASH!!!, Haiku == B_BAD_VALUE
#endif
                NextSubTest();
#if !TEST_R5
                CHK(BMimeType::GetInstalledTypes("text", NULL) != B_OK);        // R5 == CRASH!!!, Haiku == B_BAD_VALUE
#endif
                NextSubTest();
                CHK(BMimeType::GetInstalledTypes(NULL, &msg) == B_OK);          // Same as GetInstalledTypes(&msg)
//              msg.PrintToStream();
                NextSubTest();
#if !TEST_R5
                CHK(BMimeType::GetInstalledTypes(NULL, NULL) != B_OK);          // R5 == CRASH!!!, Haiku == B_BAD_VALUE
#endif
                NextSubTest();
#if !TEST_R5
                CHK(BMimeType::GetInstalledSupertypes(NULL) != B_OK);           // R5 == CRASH!!!, Haiku == B_BAD_VALUE
#endif
        }
        // Invalid supertype param to GetInstalledTypes(char *super, BMessage*)
        {
                BMessage msg;
                NextSubTest();
                CHK(!BMimeType::IsValid(testTypeSuperInvalid));
                CHK(BMimeType::GetInstalledTypes(testTypeSuperInvalid, &msg) != B_OK);                  // R5 == B_BAD_VALUE
                NextSubTest();
                CHK(BMimeType::IsValid(testTypeSuperValid));
                CHK(BMimeType::GetInstalledTypes(testTypeSuperValid, &msg) != B_OK);    // R5 == B_ENTRY_NOT_FOUND
        }
        // Normal Function -- GetInstalledTypes(BMessage*)
        // This test gets the list of installed types, then iterates through
        // the actual database directory listings and verifies they're identical.
        {
                VerifyInstalledTypes();
                BMimeType mime(testTypeApp1);
                CHK(mime.InitCheck() == B_OK);
                if (mime.IsInstalled()) {
                        CHK(mime.Delete() == B_OK);
                        VerifyInstalledTypes();
                        CHK(mime.Install() == B_OK);
                        VerifyInstalledTypes();
                } else {
                        CHK(mime.Install() == B_OK);
                        VerifyInstalledTypes();
                        CHK(mime.Delete() == B_OK);
                        VerifyInstalledTypes();
                }
        }
        // Normal Function -- GetInstalledSupertypes()/GetInstalledTypes(char*,BMessage*)
        // This test gets the list of installed super types, then iterates through
        // the actual database directory listings and verifies they're identical.

}

// Short Description

void
MimeTypeTest::ShortDescriptionTest() {
        DescriptionTest(&BMimeType::GetShortDescription, &BMimeType::SetShortDescription,
#if TEST_R5
                                           NULL
#else
                                           &BMimeType::DeleteShortDescription
#endif
        );
}

// Long Description

void
MimeTypeTest::LongDescriptionTest() {
        DescriptionTest(&BMimeType::GetLongDescription, &BMimeType::SetLongDescription,
#if TEST_R5
                                           NULL
#else
                                           &BMimeType::DeleteLongDescription
#endif
        );
}

// DescriptionTest Helper Function
void
MimeTypeTest::DescriptionTest(GetDescriptionFunc getDescr, SetDescriptionFunc setDescr,
                                                            DeleteDescriptionFunc deleteDescr)
{
        char str[B_MIME_TYPE_LENGTH+1];

        // Uninitialized
        NextSubTest();
        {
                str[0] = 0;
                BMimeType mime;
                CPPUNIT_ASSERT(mime.InitCheck() == B_NO_INIT);
                CPPUNIT_ASSERT((mime.*getDescr)(str) != B_OK);  // R5 == B_BAD_VALUE
                CPPUNIT_ASSERT((mime.*setDescr)(str) != B_OK);  // R5 == B_BAD_VALUE
        }
        // Non-installed type
        NextSubTest();
        {
                str[0] = 0;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK((mime.*getDescr)(str) != B_OK);             // R5 == B_ENTRY_NOT_FOUND
                CHK(!mime.IsInstalled());
                CHK((mime.*setDescr)(testDescr) == B_OK);       // R5 == Installs (but doesn't set), B_OK
                CHK(mime.IsInstalled());
                CHK((mime.*getDescr)(str) == B_OK);
                CHK(strcmp(str, testDescr) == 0);
        }
        // Non-installed type, NULL params
        NextSubTest();
        {
#if !TEST_R5    // NOTE: These tests crash for R5::LongDescription calls but not for R5::ShortDescription
                                // calls. Considering the general instability exihibited by most R5 calls when passed
                                // NULL pointers, however, I wouldn't suggest it, and thus they aren't even tested here.
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK((mime.*getDescr)(NULL) == B_BAD_VALUE);
                CHK(!mime.IsInstalled());
                CHK((mime.*setDescr)(NULL) == B_ENTRY_NOT_FOUND);       // Trying to delete non-existent attribute
                CHK(!mime.IsInstalled());
                CHK((mime.*setDescr)(testDescr) == B_OK);
                CHK(mime.IsInstalled());
                str[0] = 0;
                CHK((mime.*getDescr)(str) == B_OK);
                CHK(strcmp(str, testDescr) == 0);
                CHK((mime.*setDescr)(NULL) == B_OK);    // Delete the attribute
                CHK(mime.IsInstalled());
                CHK((mime.*getDescr)(str) == B_ENTRY_NOT_FOUND);
#endif
        }
        // Delete test
        NextSubTest();
        {
#if !TEST_R5
                entry_ref ref;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK((mime.*deleteDescr)() != B_OK);
                CHK(!mime.IsInstalled());
                CHK((mime.*setDescr)(testDescr) == B_OK);
                CHK(mime.IsInstalled());
                str[0] = 0;
                CHK((mime.*getDescr)(str) == B_OK);
                CHK(strcmp(str, testDescr) == 0);
                CHK((mime.*deleteDescr)() == B_OK);
                CHK(mime.IsInstalled());
                CHK((mime.*getDescr)(str) != B_OK);
#endif
        }
        // Installed type
        NextSubTest();
        {
                str[0] = 0;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                // Get() with no description installed
                CHK(mime.IsInstalled());
                CHK((mime.*getDescr)(str) == B_ENTRY_NOT_FOUND);        // R5 == B_ENTRY_NOT_FOUND
                // Initial Set()/Get()
                CHK((mime.*setDescr)(testDescr) == B_OK);
                CHK((mime.*getDescr)(str) == B_OK);
                CHK(strcmp(str, testDescr) == 0);
                // Followup Set()/Get()
                CHK((mime.*setDescr)(testDescr2) == B_OK);
                CHK((mime.*getDescr)(str) == B_OK);
                CHK(strcmp(str, testDescr2) == 0);
        }
        // Installed Type, Description Too Long
        NextSubTest();
        {
                str[0] = 0;
                CHK(strlen(longDescr) > (B_MIME_TYPE_LENGTH+1));
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                // Initial Set()/Get()
                CHK((mime.*setDescr)(longDescr) != B_OK);               // R5 == B_BAD_VALUE
                CHK((mime.*getDescr)(str) == B_ENTRY_NOT_FOUND);
                // Followup Set()/Get()
                CHK((mime.*setDescr)(testDescr) == B_OK);
                CHK((mime.*setDescr)(longDescr) != B_OK);               // R5 == B_BAD_VALUE
                CHK((mime.*getDescr)(str) == B_OK);
                CHK(strcmp(str, testDescr) == 0);
        }

}


// Preferred App

void
MimeTypeTest::PreferredAppTest() {
        char str[B_MIME_TYPE_LENGTH+1];
        sprintf(str, "%s", testSig);

        // Uninitialized
        NextSubTest();
        {
                BMimeType mime;
                CPPUNIT_ASSERT(mime.InitCheck() == B_NO_INIT);
                CPPUNIT_ASSERT(mime.GetPreferredApp(str) != B_OK);      // R5 == B_BAD_VALUE
                CPPUNIT_ASSERT(mime.SetPreferredApp(str) != B_OK);      // R5 == B_BAD_VALUE
        }
        // Non-installed type
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.GetPreferredApp(str) != B_OK);         // R5 == B_ENTRY_NOT_FOUND
                CHK(!mime.IsInstalled());
                CHK(mime.SetPreferredApp(testSig) == B_OK);     // R5 == Installs (but doesn't set), B_OK
                CHK(mime.IsInstalled());
                CHK(mime.GetPreferredApp(str) == B_OK);
                CHK(strcmp(str, testSig) == 0);
        }
        // Non-installed type, NULL params
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
#if TEST_R5
                CHK(!mime.IsInstalled());
                CHK(mime.GetPreferredApp(NULL) != B_OK);                // R5 == B_ENTRY_NOT_FOUND
                CHK(!mime.IsInstalled());
                CHK(mime.SetPreferredApp(NULL) != B_OK);                // R5 == Installs (but doesn't set), B_ENTRY_NOT_FOUND
                CHK(mime.IsInstalled());
                CHK(mime.GetPreferredApp(str) == B_ENTRY_NOT_FOUND);
#else
                CHK(!mime.IsInstalled());
                CHK(mime.GetPreferredApp(NULL) != B_OK);                // Haiku == B_BAD_VALUE
                CHK(!mime.IsInstalled());
                CHK(mime.SetPreferredApp(NULL) != B_OK);                // Haiku == B_ENTRY_NOT_FOUND
                CHK(!mime.IsInstalled());
                CHK(mime.SetPreferredApp(testSig) == B_OK);
                CHK(mime.IsInstalled());
                str[0] = 0;
                CHK(mime.GetPreferredApp(str) == B_OK);
                CHK(strcmp(str, testSig) == 0);
                CHK(mime.SetPreferredApp(NULL) == B_OK);
                CHK(mime.IsInstalled());
                str[0] = 0;
                CHK(mime.GetPreferredApp(str) != B_OK);                 // Haiku == B_ENTRY_NOT_FOUND
#endif // !TEST_R5
        }
        // Installed type, NULL params
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetPreferredApp(NULL) != B_OK);                // R5 == B_BAD_ADDRESS
                CHK(mime.SetPreferredApp(NULL) != B_OK);                // R5 == B_ENTRY_NOT_FOUND
                CHK(mime.GetPreferredApp(NULL) != B_OK);                // R5 == B_BAD_ADDRESS
        }
        // Delete test
        NextSubTest();
        {
#if !TEST_R5
                entry_ref ref;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Make sure the type isn't installed
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.DeletePreferredApp() != B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.SetPreferredApp(testSig) == B_OK);
                CHK(mime.IsInstalled());
                str[0] = 0;
                CHK(mime.GetPreferredApp(str) == B_OK);
                CHK(strcmp(str, testSig) == 0);
                CHK(mime.DeletePreferredApp() == B_OK);
                CHK(mime.IsInstalled());
                CHK(mime.GetPreferredApp(str) != B_OK);
#endif
        }
        // Installed type
        NextSubTest();
        {
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                // Get() with no description installed
                CHK(mime.IsInstalled());
                CHK(mime.GetPreferredApp(str) == B_ENTRY_NOT_FOUND);    // R5 == B_ENTRY_NOT_FOUND
                // Initial Set()/Get()
                CHK(mime.SetPreferredApp(testSig) == B_OK);
                CHK(mime.GetPreferredApp(str) == B_OK);
                CHK(strcmp(str, testSig) == 0);
                // Followup Set()/Get()
                CHK(mime.SetPreferredApp(testSig2) == B_OK);
                CHK(mime.GetPreferredApp(str) == B_OK);
                CHK(strcmp(str, testSig2) == 0);
        }
        // Installed Type, Signature Too Long
        NextSubTest();
        {
                CHK(strlen(longDescr) > (B_MIME_TYPE_LENGTH+1));
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                // Uninstall then reinstall to clear attributes
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                // Initial Set()/Get()
                CHK(mime.SetPreferredApp(longSig) != B_OK);             // R5 == B_BAD_VALUE
                CHK(mime.GetPreferredApp(str) == B_ENTRY_NOT_FOUND);
                // Followup Set()/Get()
                CHK(mime.SetPreferredApp(testSig) == B_OK);
                CHK(mime.SetPreferredApp(longSig) != B_OK);             // R5 == B_BAD_VALUE
                CHK(mime.GetPreferredApp(str) == B_OK);
                CHK(strcmp(str, testSig) == 0);
        }

}

// Converts every character in str to lowercase and places
// the result in result.
void
to_lower(const char *str, std::string &result) {
        CHK(str != NULL);
        result = "";
        for (uint i = 0; i < strlen(str); i++)
                result += tolower(str[i]);
}

std::string
to_lower(const char *str) {
        std::string result;
        to_lower(str, result);
        return result;
}


// Manually removes the file in the MIME database corresponding to
// the given MIME type
void
remove_type(const char *type, const char *databaseDir) {
        CHK(type != NULL);

        // Since the MIME types are converted to lower case before their
        // corresponding file is created in the database, we need to do
        // the same
        std::string typeLower;
        to_lower(type, typeLower);

        BEntry entry((std::string(mimeDatabaseDir) + "/" + typeLower).c_str());
        CHK(entry.InitCheck() == B_OK);
        if (entry.Exists())
                CHK(entry.Remove() == B_OK);
        CHK(!entry.Exists());
}

// Manually verifies that the file in the MIME database corresponding to
// the given MIME type exists
bool
type_exists(const char *type, const char *databaseDir) {
        CHK(type != NULL);

        // Since the MIME types are converted to lower case before their
        // corresponding file is created in the database, we need to do
        // the same
        std::string typeLower;
        to_lower(type, typeLower);

        BEntry entry((std::string(databaseDir) + "/" + typeLower).c_str());
        CHK(entry.InitCheck() == B_OK);
        return entry.Exists();
}

void
MimeTypeTest::InstallDeleteTest() {
        // Uninitialzized
        NextSubTest();
        {
                BMimeType mime;
                CHK(mime.InitCheck() == B_NO_INIT);
                CHK(!mime.IsInstalled());
                CHK(mime.Install() != B_OK);    // R5 == B_BAD_VALUE
                CHK(mime.Delete() != B_OK);     // R5 == B_BAD_VALUE
        }
        // Invalid Type String
        NextSubTest();
        {
                BMimeType mime(testTypeInvalid);
                CHK(mime.InitCheck() != B_OK);  // R5 == B_BAD_VALUE
                CHK(!mime.IsInstalled());
                CHK(mime.Install() != B_OK);    // R5 == B_BAD_VALUE
                CHK(mime.Delete() != B_OK);     // R5 == B_BAD_VALUE
        }
        // Normal function
        NextSubTest();
        {
                remove_type(testType);
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                CHK(!mime.IsInstalled());
                CHK(mime.Delete() != B_OK);     // R5 == B_ENTRY_NOT_FOUND, Haiku == B_ENTRY_NOT_FOUND
                CHK(!type_exists(testType));
                CHK(mime.Install() == B_OK);
                CHK(type_exists(testType));
                CHK(mime.IsInstalled());
#if !TEST_R5
                CHK(mime.Install() != B_OK);    // We ought to return something standard and logical here, as R5 is random
#endif
                CHK(mime.Delete() == B_OK);
                CHK(!type_exists(testType));
                CHK(!mime.IsInstalled());
        }

}

void FillWithMimeTypes(ContainerAdapter &container, BMessage &typeMessage, const char* fieldName) {
        type_code type;
        int32 count;
        status_t err;

//      typeMessage.PrintToStream();

        // Get a count of types in the message
        err = typeMessage.GetInfo(fieldName, &type, &count);
        if (err == B_NAME_NOT_FOUND)
                count = 0;                      // No such types installed in the database! :-)
        else
                CHK(err == B_OK);       // Any other error is unacceptable

        // Add them all to the container, after converting to lowercase and
        // checking validity
        for (int i = 0; i < count; i++) {
                char *str;
                CHK(typeMessage.FindString(fieldName, i, (const char**)&str) == B_OK);
                std::string strLower;
                to_lower(str, strLower);
                // Make sure it's a valid type string, since the R5::GetInstalled*Types()
                // functions do no such verification, and we ignore invalid type files
                // in the database.
                if (BMimeType::IsValid(strLower.c_str()))
                        container.Add(strLower);
        }
}

bool
types_fields_are_identical(const BMessage &msg1, const BMessage &msg2)
{
        const char *str1;
        const char *str2;
        int i = 0;
        bool result = true;
        while (true) {
                status_t err1 = msg1.FindString(typesField, i, &str1);
                status_t err2 = msg2.FindString(typesField, i, &str2);
                if (err1 != err2) {
                        result = false;
                        break;
                }
                if (err1 != B_OK)
                        break;
                result &= to_lower(str1) == to_lower(str2);
                i++;
        }
        return result;
}

bool
is_supporting_app_for_all_types_in_message(const char *app, const BMessage &msg)
{
        const char *str;
        bool result = true;
        for (int i = 0; msg.FindString(typesField, i, &str) == B_OK; i++) {
                BMimeType supportedType(str);
                BMessage appMsg;

                // Get a list of supporting apps
                CHK(supportedType.InitCheck() == B_OK);
                CHK(supportedType.GetSupportingApps(&appMsg) == B_OK);
//              cout << "-----------------------------------------------------------" << endl;
//              cout << str << endl;
//              cout << "-----------------------------------------------------------" << endl;
//              appMsg.PrintToStream();

                // Look for our supporting app
                int32 directlySupportingAppsCount;
                CHK(appMsg.FindInt32("be:sub", &directlySupportingAppsCount) == B_OK);
                bool foundType = false;
                const char *supportingApp;
                for (int j = 0;
                           j < directlySupportingAppsCount
                             && appMsg.FindString(applicationsField, &supportingApp) == B_OK;
                               j++)
                {
                        foundType |= to_lower(app) == to_lower(supportingApp);
                }
                result &= foundType;
        }
        return result;
}

void
MimeTypeTest::SupportedTypesTest() {
#if TEST_R5
        Outputf("(no tests actually performed for R5 version)\n");
#else
        // Create some messages to sling around
        const int32 WHAT = 0;
        BMessage msg1a(WHAT), msg1b(WHAT), msg2(WHAT), msg3(WHAT), msgEmpty(WHAT);

        CHK(msg3.AddString(typesField, testType1) == B_OK);
        CHK(msg3.AddString(typesField, testType2) == B_OK);
        CHK(msg3.AddString(typesField, testType3) == B_OK);

        CHK(msg2.AddString(typesField, testType2) == B_OK);
        CHK(msg2.AddString(typesField, testType3) == B_OK);

        CHK(msg1a.AddString(typesField, testType5) == B_OK);

        CHK(msg1b.AddString(typesField, testType2) == B_OK);

        CHK(msg1a == msg1a);
        CHK(msg1b == msg1b);
        CHK(msg2 == msg2);
        CHK(msg3 == msg3);
        CHK(msg1a != msg2);
        CHK(msg1a != msg3);
        CHK(msg1a != msgEmpty);

        // Uninitialized
        NextSubTest();
        {
                BMimeType mime;
                BMessage msg;

                CHK(mime.InitCheck() == B_NO_INIT);
                CHK(mime.SetSupportedTypes(&msg, true) != B_OK);
                CHK(mime.SetSupportedTypes(&msg, false) != B_OK);
                CHK(mime.GetSupportedTypes(&msg) != B_OK);
                CHK(mime.DeleteSupportedTypes() != B_OK);
        }

        // Test that deleting a type from the database also removes
        // the app as a supporting app for all types it previously
        // supported
        NextSubTest();
        {
                BMessage msg;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);

                // Set a list of supported types
                CHK(mime.SetSupportedTypes(&msg3, true) == B_OK);

                // Verify that each of those types now lists the
                // type as a directly supporting app
                CHK(is_supporting_app_for_all_types_in_message(testType, msg3) == true);

                // Delete the type
                CHK(mime.Delete() == B_OK);

                // Verify that each of those types no longer lists the
                // type as a directly supporting app
                CHK(is_supporting_app_for_all_types_in_message(testType, msg3) == false);
        }

        // Test that SetSupportedTypes(..., false) does not remove the app as a supporting
        // app for newly unsupported types, while SetSupportedTypes(..., true) does. Also
        // test that supported types stranded by multiple sequential calls to
        // SetSupportedTypes(..., false) are properly updated so as to no longer list the
        // app as a supporting app once SetSupportedTypes(..., true) is finally called.
        NextSubTest();
        {
                BMessage msg;
                BMimeType mime(testType);
                CHK(mime.InitCheck() == B_OK);

                // Uninstall then reinstall to clear attributes, etc
                if (mime.IsInstalled())
                        CHK(mime.Delete() == B_OK);
                if (!mime.IsInstalled())
                        CHK(mime.Install() == B_OK);
                CHK(mime.IsInstalled());

                // Set a list of supported types
                CHK(mime.SetSupportedTypes(&msg3, true) == B_OK);
                CHK(mime.GetSupportedTypes(&msg) == B_OK);
                CHK(types_fields_are_identical(msg3, msg) == true);
                CHK(types_fields_are_identical(msg2, msg) == false);
                CHK(types_fields_are_identical(msg1a, msg) == false);
                CHK(types_fields_are_identical(msg1b, msg) == false);
                CHK(types_fields_are_identical(msgEmpty, msg) == false);

                // Verify that each of those types now lists the
                // type as a directly supporting app
                CHK(is_supporting_app_for_all_types_in_message(testType, msg3) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg2) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1a) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1b) == true);

                // Set (no sync) to a new list of supported types containing one
                // fewer type than the original list
                CHK(mime.SetSupportedTypes(&msg2, false) == B_OK);
                CHK(mime.GetSupportedTypes(&msg) == B_OK);
                CHK(types_fields_are_identical(msg3, msg) == false);
                CHK(types_fields_are_identical(msg2, msg) == true);
                CHK(types_fields_are_identical(msg1a, msg) == false);
                CHK(types_fields_are_identical(msg1b, msg) == false);
                CHK(types_fields_are_identical(msgEmpty, msg) == false);

                // Verify that the app is still listed as a supporting app for
                // *all* of the originally supported types (even the one no longer
                // listed as being supported)
                CHK(is_supporting_app_for_all_types_in_message(testType, msg3) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg2) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1a) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1b) == true);

                // Set (no sync) to a new list of supported types containing an
                // entirely new, never supported type
                CHK(mime.SetSupportedTypes(&msg1a, false) == B_OK);
                CHK(mime.GetSupportedTypes(&msg) == B_OK);
                CHK(types_fields_are_identical(msg3, msg) == false);
                CHK(types_fields_are_identical(msg2, msg) == false);
                CHK(types_fields_are_identical(msg1a, msg) == true);
                CHK(types_fields_are_identical(msg1b, msg) == false);
                CHK(types_fields_are_identical(msgEmpty, msg) == false);

                // Verify that the app is still listed as a supporting app for
                // *all* of the originally supported types (none of which are
                // supported any longer) as well as the newly supported type.
                CHK(is_supporting_app_for_all_types_in_message(testType, msg3) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg2) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1a) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1b) == true);

                // Set (no sync) to a new list of supported types containing only
                // one of the originally supported types that had been previously
                // removed.
                CHK(mime.SetSupportedTypes(&msg1b, false) == B_OK);
                CHK(mime.GetSupportedTypes(&msg) == B_OK);
                CHK(types_fields_are_identical(msg3, msg) == false);
                CHK(types_fields_are_identical(msg2, msg) == false);
                CHK(types_fields_are_identical(msg1a, msg) == false);
                CHK(types_fields_are_identical(msg1b, msg) == true);
                CHK(types_fields_are_identical(msgEmpty, msg) == false);

                // Verify that the app is still listed as a supporting app for
                // *all* of the originally supported types (only one of which is
                // supported any longer) as well as the previous supported type.
                CHK(is_supporting_app_for_all_types_in_message(testType, msg3) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg2) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1a) == true);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1b) == true);

                // Set (with sync) to the same list as last time (containing the
                // one type from the original list of supported types)
                CHK(mime.SetSupportedTypes(&msg1b, true) == B_OK);
                CHK(mime.GetSupportedTypes(&msg) == B_OK);
                CHK(types_fields_are_identical(msg3, msg) == false);
                CHK(types_fields_are_identical(msg2, msg) == false);
                CHK(types_fields_are_identical(msg1a, msg) == false);
                CHK(types_fields_are_identical(msg1b, msg) == true);
                CHK(types_fields_are_identical(msgEmpty, msg) == false);

                // Verify that the app is now only listed as a supporting app for the
                // most recently supported type.
                CHK(is_supporting_app_for_all_types_in_message(testType, msg3) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg2) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1a) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1b) == true);

                // Test SetSupportedTypes(NULL, false) for shits and giggles
                CHK(mime.SetSupportedTypes(NULL, false) == B_OK);
                CHK(mime.GetSupportedTypes(&msg) == B_ENTRY_NOT_FOUND);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg3) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg2) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1a) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1b) == true);

                // Now test that SetSupportedTypes(NULL, true) updates the supporting
                // apps mappings, even if the supported types attribute has already
                // been removed.
                CHK(mime.SetSupportedTypes(NULL, true) == B_ENTRY_NOT_FOUND);
                CHK(mime.GetSupportedTypes(&msg) == B_ENTRY_NOT_FOUND);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg3) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg2) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1a) == false);
                CHK(is_supporting_app_for_all_types_in_message(testType, msg1b) == false);
        }
#endif  // #if TEST_R5 else
}

void
MimeTypeTest::SupportingAppsTest() {
/*      {
                BMessage msg;
                BMimeType::GetInstalledTypes(&msg);
                msg.PrintToStream();
        }
        {
                BMessage msg;
                BMimeType mime("application/octet-stream");
                CHK(mime.InitCheck() == B_OK);
                CHK(mime.GetSupportingApps(&msg) == B_OK);
                msg.PrintToStream();
        }
        {
                BMessage msg;
                BMimeType mime("text");
                CHK(mime.InitCheck() == B_OK);
                CHK(mime.GetSupportingApps(&msg) == B_OK);
                msg.PrintToStream();
        }
        {
                BMessage msg;
                BMimeType mime("text/html");
                CHK(mime.InitCheck() == B_OK);
                CHK(mime.GetSupportingApps(&msg) == B_OK);
                msg.PrintToStream();
        } */
        NextSubTest();
        if (true)
        {
                std::set<std::string> typeList;                                                 // Stores all installed MIME types
                std::set<std::string> appList;                                                  // Stores all installed application subtypes
                std::map< std::string, std::set<std::string> > typeAppMap;      // Stores mapping of types to apps that support them
                std::map< std::string, std::set<std::string> > fakeTypeAppMap;  // Used to keep timing info for R5 and Haiku tests orthogonal

                // Get a list of all the types in the database
                {
                        BMessage msg;
                        CHK(BMimeType::GetInstalledTypes(&msg) == B_OK);
                        SetAdapter typeAdapter(typeList);
                        FillWithMimeTypes(typeAdapter, msg, "types");
                }

                // Get a list of all the apps in the database
                {
                        BMessage msg;
                        CHK(BMimeType::GetInstalledTypes(applicationSupertype, &msg) == B_OK);
                        SetAdapter appAdapter(appList);
                        FillWithMimeTypes(appAdapter, msg, "types");
                }

                // For each app in the database, manually get a list of the MIME types
                // it supports by reading its META:FILE_TYPES attribute from the database,
                // and add the app to the type->app map for each such type
                {
                        std::set<std::string>::iterator i;
                        for (i = appList.begin(); i != appList.end(); i++) {
                                // Grab the next application
                                std::string app = *i;

                                // The leaf is all we're interested in -- it's the subtype
//                              CHK(StorageKit::split_path(app.c_str(), dir, leaf) == B_OK);
                                std::string appFile = std::string(mimeDatabaseDir) + "/" + app;
//                              printf("'%s'\n", appFile.c_str());
                                BNode node(appFile.c_str());
                                CHK(node.InitCheck() == B_OK);

                                // Find out how much data there is in the META:FILE_TYPES attribute
                                // (assuming it even exists, which it may not...)
                                attr_info info;
                                if (node.GetAttrInfo("META:FILE_TYPES", &info) == B_OK) {
//                                      printf("attr_info: type == %lx, size == %lld\n", info.type, info.size);
                                        // Attribute exists, so alloc a buffer and read it
                                        char *buffer = new char[info.size+1];
                                        CHK(node.ReadAttr("META:FILE_TYPES", B_MESSAGE_TYPE, 0, buffer, info.size) == info.size);
                                        BMessage msg;
                                        if (msg.Unflatten(buffer) == B_OK) {
//                                              msg.PrintToStream();

                                                // Fill up a list with all the supported types
                                                std::set<std::string> supportList;
                                                SetAdapter supportAdapter(supportList);
                                                FillWithMimeTypes(supportAdapter, msg, "types");

                                                // For each type, add the current application as a supporting
                                                // app in our type->apps map
                                                for (std::set<std::string>::iterator type = supportList.begin();
                                                                type != supportList.end();
                                                                        type++)
                                                {
                                                        NextSubTest();
                                                        typeAppMap[*type].insert(app);
                                                        fakeTypeAppMap[*type].insert(app);
                                                }
                                        } else {
                                                // Just in case some bozo writes something other than a flattened
                                                // BMessage to the META:FILE_TYPES attribute, we'll only issue a
                                                // warning when the BMessage can't unflatten itself
                                                printf("Warning: Unable to unflatten META:FILE_TYPES attribute for '%s' type.\n",
                                                        app.c_str());
                                        }

                                        delete buffer;

                                }
                        }
                }

//#if !TEST_R5
                // Now, add in all the types listed in MIME_DB_DIR/__mime_table
                {
                        BEntry entry((std::string(mimeDatabaseDir) + "/__mime_table").c_str());
                        CHK(entry.InitCheck() == B_OK);
                        if (entry.Exists()) {
                                BFile file(&entry, B_READ_ONLY);
                                CHK(file.InitCheck() == B_OK);
                                BMessage msg;
                                CHK(msg.Unflatten(&file) == B_OK);

//                              msg.PrintToStream();

                                char *type;
                                uint32 typeVal;
                                int32 count;
                                for (int i = 0; msg.GetInfo(B_STRING_TYPE, i, &type, &typeVal, &count) == B_OK; i++ ) {
                                        // Add all the associated applications. Interestingly (or maybe not),
                                        // any types appearing ONLY in the __mime_table and not in the mime
                                        // database fail when GetSupportingApps is called on them. Thus, we
                                        // add them to the type->app map, but not to the list of types.
                                        const char *app;
                                        for (int j = 0; j < count; j++) {
                                                CHK(msg.FindString(type, j, &app) == B_OK);
#if TEST_R5
                                                typeAppMap[type].insert(to_lower(app));
#else
                                                fakeTypeAppMap[type].insert(to_lower(app));
#endif
                                        }
                                }
                        }
                }
//#endif

                // For each installed type, get a list of the supported apps, and
                // verify that the list matches the list we generated. Also check
                // that the list of apps for the type's supertype (if it exists)
                // is a subset of the list we generated for said supertype.
                for (std::set<std::string>::iterator i = typeList.begin(); i != typeList.end(); i++) {
                        // Get the current type
                        std::string type = *i;
                        BMimeType mime(type.c_str());
                        CHK(mime.InitCheck() == B_OK);
//                      printf("------------------------------------------------------------\n");
//                      printf("%s\n", type.c_str());

                        // Get the set of supporting apps for this type (and its supertype, if
                        // it's not a supertype itself) that we discovered by manually culling
                        // the database
                        std::set<std::string> appSetSuper;
                        BMimeType superType;
                        if (mime.GetSupertype(&superType) == B_OK)
                                appSetSuper = typeAppMap[superType.Type()];             // Copy the supertype
/*
                        printf("sub.size == %ld\n", appSet.size());
                        std::set<std::string>::iterator i;
                        for (i = appSet.begin(); i != appSet.end(); i++) {
                                printf("  %s\n", (*i).c_str());
                        }
                        printf("super.size == %ld\n", appSetSuper.size());
                        for (i = appSetSuper.begin(); i != appSetSuper.end(); i++) {
                                printf("  %s\n", (*i).c_str());
                        }
                        char* str;
                        if (mime.GetPreferredApp(str) == B_OK) {
                                printf("preferred app:\n");
                                printf("  %s\n", str);
                        }
*/
                        // Get the set of supporting apps via GetSupportingApps(), then
                        // add them to a list.
                        BMessage msg;
                        CHK(mime.GetSupportingApps(&msg) == B_OK);
                        std::queue<std::string> appList;
                        QueueAdapter appAdapter(appList);
                        FillWithMimeTypes(appAdapter, msg, "applications");

//                      msg.PrintToStream();


                }
        }
}

void
MimeTypeTest::WildcardAppsTest() {
        // NULL param
        NextSubTest();
        {
#if TEST_R5
                CHK(BMimeType::GetWildcardApps(NULL) == B_OK);                  // R5 == B_OK (???)
#else
                CHK(BMimeType::GetWildcardApps(NULL) == B_BAD_VALUE);
#endif
        }
        // Normal function (compare to BMimeType("application/octet-stream").GetSupportingApps())
        NextSubTest();
        {
                BMessage msg1, msg2;
                CHK(BMimeType::GetWildcardApps(&msg1) == B_OK);
                BMimeType mime(wildcardType);
                CHK(mime.InitCheck() == B_OK);
                CHK(mime.GetSupportingApps(&msg2) == B_OK);
                CHK(msg1 == msg2);
        }
}


// init_long_types
static
void
init_long_types(char *notTooLongType, char *tooLongType)
{
// R5: Allows buffer sizes up to `B_MIME_TYPE_LENGTH + 1'
// Haiku: We stay consistent: `*_LENGTH' defines the buffer size.
#ifdef TEST_R5
        const int notTooLongLength = B_MIME_TYPE_LENGTH;
#else
        const int notTooLongLength = B_MIME_TYPE_LENGTH - 1;
#endif
        const int tooLongLength = notTooLongLength + 1;
        strcpy(notTooLongType, "image/");
        memset(notTooLongType + strlen(notTooLongType), 'a',
                   notTooLongLength - strlen(notTooLongType));
        notTooLongType[notTooLongLength] = '\0';
        strcpy(tooLongType, "image/");
        memset(tooLongType + strlen(tooLongType), 'a',
                   tooLongLength - strlen(tooLongType));
        tooLongType[tooLongLength] = '\0';
}

// InitTest
void
MimeTypeTest::InitTest()
{
        // tests:
        // * constructors
        // * SetTo(), SetType()
        // * Unset()
        // * InitCheck()
        // (* Type())

        // We test only a few types here. Exhausting testing is done in
        // ValidityTest().
        const char *validType   = "image/gif";
        const char *validType2  = "application/octet-stream";
        const char *invalidType = "invalid type";
        char notTooLongType[B_MIME_TYPE_LENGTH + 3];
        char tooLongType[B_MIME_TYPE_LENGTH + 3];
        init_long_types(notTooLongType, tooLongType);

        // default constructor
        NextSubTest();
        {
                BMimeType type;
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }

        // BMimeType(const char *)
        // valid type
        NextSubTest();
        {
                BMimeType type(validType);
                CHK(type.InitCheck() == B_OK);
                CHK(string(type.Type()) == validType);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }
        // invalid type
        NextSubTest();
        {
                BMimeType type(invalidType);
                CHK(type.InitCheck() == B_BAD_VALUE);
                CHK(type.Type() == NULL);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }
        // long, but not too long type
        NextSubTest();
        {
                BMimeType type(notTooLongType);
                CHK(type.InitCheck() == B_OK);
                CHK(string(type.Type()) == notTooLongType);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }
        // too long type
        NextSubTest();
        {
                BMimeType type(tooLongType);
                CHK(type.InitCheck() == B_BAD_VALUE);
                CHK(type.Type() == NULL);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }

        // SetTo()
        // valid type
        NextSubTest();
        {
                BMimeType type;
                CHK(type.SetTo(validType) == B_OK);
                CHK(type.InitCheck() == B_OK);
                CHK(string(type.Type()) == validType);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }
        // invalid type
        NextSubTest();
        {
                BMimeType type;
                CHK(type.SetTo(invalidType) == B_BAD_VALUE);
                CHK(type.InitCheck() == B_BAD_VALUE);
                CHK(type.Type() == NULL);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }
        // long, but not too long type
        NextSubTest();
        {
                BMimeType type;
                CHK(type.SetTo(notTooLongType) == B_OK);
                CHK(type.InitCheck() == B_OK);
                CHK(string(type.Type()) == notTooLongType);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }
        // too long type
        NextSubTest();
        {
                BMimeType type;
                CHK(type.SetTo(tooLongType) == B_BAD_VALUE);
                CHK(type.InitCheck() == B_BAD_VALUE);
                CHK(type.Type() == NULL);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }

        // SetType()
        // valid type
        NextSubTest();
        {
                BMimeType type;
                CHK(type.SetType(validType) == B_OK);
                CHK(type.InitCheck() == B_OK);
                CHK(string(type.Type()) == validType);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }
        // invalid type
        NextSubTest();
        {
                BMimeType type;
                CHK(type.SetType(invalidType) == B_BAD_VALUE);
                CHK(type.InitCheck() == B_BAD_VALUE);
                CHK(type.Type() == NULL);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }
        // long, but not too long type
        NextSubTest();
        {
                BMimeType type;
                CHK(type.SetType(notTooLongType) == B_OK);
                CHK(type.InitCheck() == B_OK);
                CHK(string(type.Type()) == notTooLongType);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }
        // too long type
        NextSubTest();
        {
                BMimeType type;
                CHK(type.SetType(tooLongType) == B_BAD_VALUE);
                CHK(type.InitCheck() == B_BAD_VALUE);
                CHK(type.Type() == NULL);
                type.Unset();
                CHK(type.InitCheck() == B_NO_INIT);
                CHK(type.Type() == NULL);
        }

        // reinitialization
        NextSubTest();
        {
                BMimeType type(validType);
                CHK(type.InitCheck() == B_OK);
                CHK(string(type.Type()) == validType);
                CHK(type.SetTo(validType2) == B_OK);
                CHK(type.InitCheck() == B_OK);
                CHK(string(type.Type()) == validType2);
        }
        // bad args
        NextSubTest();
        {
                BMimeType type(NULL);
                CHK(type.Type() == NULL);
                CHK(type.InitCheck() != B_OK);          // R5 == B_NO_INIT, Haiku == B_BAD_VALUE
                CHK(type.Type() == NULL);
                CHK(type.SetTo(NULL) != B_OK);          // R5 == B_NO_INIT, Haiku == B_BAD_VALUE
                CHK(type.Type() == NULL);
                CHK(type.SetType(NULL) != B_OK);        // R5 == B_NO_INIT, Haiku == B_BAD_VALUE
                CHK(type.Type() == NULL);
        }
}

// StringTest
void
MimeTypeTest::StringTest()
{
        // tests:
        // * IsValid() (static/non static)
        // * Type()
        // * IsSupertypeOnly()
        // * GetSupertype()
        // * Contains()
        // * operator==()

        char notTooLongType[B_MIME_TYPE_LENGTH + 3];
        char tooLongType[B_MIME_TYPE_LENGTH + 3];
        init_long_types(notTooLongType, tooLongType);
        struct mime_type_test {
                const char      *type;
                bool            super_type;
                status_t        error;
        };
        mime_type_test tests[] = {
                // valid types
                { "application",                                        true,   B_OK, },
                { "application/octet-stream",           false,  B_OK, },
                { "audio",                                                      true,   B_OK, },
                { "audio/x-aiff",                                       false,  B_OK, },
                { "image",                                                      true,   B_OK, },
                { "image/gif",                                          false,  B_OK, },
                { "message",                                            true,   B_OK, },
                { "message/rfc822",                                     false,  B_OK, },
                { "multipart",                                          true,   B_OK, },
                { "multipart/mixed",                            false,  B_OK, },
                { "text",                                                       true,   B_OK, },
                { "text/plain",                                         false,  B_OK, },
                { "video",                                                      true,   B_OK, },
                { "video/x-msvideo",                            false,  B_OK, },
                { "unknown",                                            true,   B_OK, },
                { "unknown/mime-type",                          false,  B_OK, },
                { "$%&./`'~*+#|!^",                                     false,  B_OK, },
                // invalid types
                { "",                                                           false,  B_BAD_VALUE, },
                { "application/",                                       false,  B_BAD_VALUE, },
                { "audio/",                                                     false,  B_BAD_VALUE, },
                { "image/",                                                     false,  B_BAD_VALUE, },
                { "message/",                                           false,  B_BAD_VALUE, },
                { "multipart/",                                         false,  B_BAD_VALUE, },
                { "text/",                                                      false,  B_BAD_VALUE, },
                { "video/",                                                     false,  B_BAD_VALUE, },
                { "unknown/",                                           false,  B_BAD_VALUE, },
                { "/gif",                                                       false,  B_BAD_VALUE, },
                { "image/very/nice",                            false,  B_BAD_VALUE, },
                { "tex t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla in",                                        false,  B_BAD_VALUE, },
                { "tex\tt/plain",                                       false,  B_BAD_VALUE, },
                { "text/pla\tin",                                       false,  B_BAD_VALUE, },
                { "tex\nt/plain",                                       false,  B_BAD_VALUE, },
                { "text/pla\nin",                                       false,  B_BAD_VALUE, },
                { "tex<t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla<in",                                        false,  B_BAD_VALUE, },
                { "tex>t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla>in",                                        false,  B_BAD_VALUE, },
                { "tex@t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla@in",                                        false,  B_BAD_VALUE, },
                { "tex,t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla,in",                                        false,  B_BAD_VALUE, },
                { "tex;t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla;in",                                        false,  B_BAD_VALUE, },
                { "tex:t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla:in",                                        false,  B_BAD_VALUE, },
                { "tex\"t/plain",                                       false,  B_BAD_VALUE, },
                { "text/pla\"in",                                       false,  B_BAD_VALUE, },
                { "tex(t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla(in",                                        false,  B_BAD_VALUE, },
                { "tex)t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla)in",                                        false,  B_BAD_VALUE, },
                { "tex[t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla[in",                                        false,  B_BAD_VALUE, },
                { "tex]t/pla]in",                                       false,  B_BAD_VALUE, },
                { "tex?t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla?in",                                        false,  B_BAD_VALUE, },
                { "tex=t/plain",                                        false,  B_BAD_VALUE, },
                { "text/pla=in",                                        false,  B_BAD_VALUE, },
                { "tex\\t/plain",                                       false,  B_BAD_VALUE, },
                { "text/pla\\in",                                       false,  B_BAD_VALUE, },
                // (not) too long types
                { notTooLongType,                                       false,  B_OK, },
                { tooLongType,                                          false,  B_BAD_VALUE, },
        };
        int32 testCount = sizeof(tests) / sizeof(mime_type_test);
        // test loop
        for (int32 i = 0; i < testCount; i++) {
                NextSubTest();
                mime_type_test &test = tests[i];
                BMimeType type(test.type);
                CHK(type.InitCheck() == test.error);
                bool valid = (test.error == B_OK);
                bool validSuper = (valid && test.super_type);
                // Type()
                if (valid)
                        CHK(string(type.Type()) == test.type);
                else
                        CHK(type.Type() == NULL);
                // IsValid(), IsSuperTypeOnly()
                CHK(type.IsValid() == valid);
                CHK(type.IsSupertypeOnly() == validSuper);
                CHK(BMimeType::IsValid(test.type) == valid);
                // GetSupertype()
                if (valid && !validSuper) {
                        BMimeType super;
                        CHK(type.GetSupertype(&super) == B_OK);
                        CHK(super.InitCheck() == B_OK);
                        CHK(super.Contains(&type) == true);
                        BString typeString(test.type);
                        BString superString(typeString.String(),
                                                                typeString.FindFirst('/'));
                        CHK(superString == super.Type());
                } else {
                        BMimeType super;
                        CHK(type.GetSupertype(&super) == B_BAD_VALUE);
                }
                // Contains(), ==
                for (int32 k = 0; k < testCount; k++) {
                        mime_type_test &test2 = tests[k];
                        BMimeType type2(test2.type);
                        CHK(type2.InitCheck() == test2.error);
                        bool valid2 = (test2.error == B_OK);
                        bool validSuper2 = (valid && test2.super_type);
                        bool equal = (!strcmp(test.type, test2.type));
                        // ==
                        if (valid || valid2) {
                                CHK((type == type2) == equal);
                                CHK((type == test2.type) == equal);
                        } else {
                                CHK((type == type2) == false);
                                CHK((type == test2.type) == false);
                        }
                        // Contains()
                        if (valid || valid2) {
                                if (equal)
                                        CHK(type.Contains(&type2) == true);
                                else if (validSuper && valid2 && !validSuper2) {
                                        BMimeType super2;
                                        CHK(type2.GetSupertype(&super2) == B_OK);
                                        bool contains = string(super2.Type()) == type.Type();
                                        CHK(type.Contains(&type2) == contains);
                                } else
                                        CHK(type.Contains(&type2) == false);
                        } else
                                CHK(type.Contains(&type2) == false);
                }
        }
        // bad args
        NextSubTest();
        {
                BMimeType type("image/gif");
// R5: crashes when passing NULL
#if !TEST_R5
                CHK(BMimeType::IsValid(NULL) == false);
                CHK(type.GetSupertype(NULL) == B_BAD_VALUE);
                CHK(type.Contains(NULL) == false);
#endif
                CHK((type == NULL) == false);
        }
}

// an easy to construct equivalent of a notification message
class NotificationMessage {
public:
        NotificationMessage(int32 which, string type, string extraType,
                                                bool largeIcon)
                : which(which), type(type), hasExtraType(true), extraType(extraType),
                  hasLargeIcon(true), largeIcon(largeIcon)
        {
        }

        NotificationMessage(int32 which, string type, string extraType)
                : which(which), type(type), hasExtraType(true), extraType(extraType),
                  hasLargeIcon(false), largeIcon(false)
        {
        }

        NotificationMessage(int32 which, string type, bool largeIcon)
                : which(which), type(type), hasExtraType(false), extraType(),
                  hasLargeIcon(true), largeIcon(largeIcon)
        {
        }

        NotificationMessage(int32 which, string type)
                : which(which), type(type), hasExtraType(false), extraType(),
                  hasLargeIcon(false), largeIcon(false)
        {
        }

public:
        int32   which;
        string  type;
        bool    hasExtraType;
        string  extraType;
        bool    hasLargeIcon;
        bool    largeIcon;
};

// FillAttrInfo
static
void
FillAttrInfo(BMessage &info, int32 variation = 0)
{
        switch (variation) {
                case 0:
                default:
                        CHK(info.AddString("attr:name", "attribute1") == B_OK);
                        CHK(info.AddString("attr:public_name", "Nice Attribute1") == B_OK);
                        CHK(info.AddInt32("attr:type", B_STRING_TYPE) == B_OK);
                        CHK(info.AddBool("attr:public", true) == B_OK);
                        CHK(info.AddBool("attr:editable", true) == B_OK);
                        break;
                case 1:
                        CHK(info.AddString("attr:name", "attribute2") == B_OK);
                        CHK(info.AddString("attr:public_name", "Nice Attribute2") == B_OK);
                        CHK(info.AddInt32("attr:type", B_BOOL_TYPE) == B_OK);
                        CHK(info.AddBool("attr:public", false) == B_OK);
                        CHK(info.AddBool("attr:editable", false) == B_OK);
                        break;
        }
}

// MonitoringTest
void
MimeTypeTest::MonitoringTest()
{
        // tests:
        // * Start/StopWatching()
        // * updates

        // test:
        // * StartWatching()
        // * change something, check message queue (not empty)
        //   - add type
        //   - set icon, preferred app, attr info, file ext., short/long desc.,
        //     icon for, app hint, sniffer rule
        //   - remove type
        // * StopWatching(anotherTarget)
        // * change something, check message queue (not empty)
        // * StopWatching()
        // * change something, check message queue (empty)

        CHK(fApplication != NULL);
        NextSubTest();
        // StartWatching()
        BMessenger target(&fApplication->Handler(), fApplication);
        CHK(BMimeType::StartWatching(target) == B_OK);
        // install
        BMimeType type(testType);
        CHK(type.InitCheck() == B_OK);
        CHK(type.IsInstalled() == false);
        CHK(type.Install() == B_OK);
        // icon
        IconHelper iconHelperLarge(B_LARGE_ICON);
        IconHelper iconHelperMini(B_MINI_ICON);
        CHK(type.SetIcon(iconHelperLarge.Bitmap1(), B_LARGE_ICON) == B_OK);
        CHK(type.SetIcon(iconHelperMini.Bitmap1(), B_MINI_ICON) == B_OK);
        // preferred app
        CHK(type.SetPreferredApp(testTypeApp) == B_OK);
        // attr info
        BMessage attrInfo;
        FillAttrInfo(attrInfo);
        CHK(type.SetAttrInfo(&attrInfo) == B_OK);
        // file extensions
        BMessage extensions;
        CHK(extensions.AddString("extensions", "arg") == B_OK);
        CHK(extensions.AddString("extensions", "ugh") == B_OK);
        CHK(type.SetFileExtensions(&extensions) == B_OK);
        // long/short description
        CHK(type.SetLongDescription("quite short for a long description") == B_OK);
        CHK(type.SetShortDescription("short description") == B_OK);
        // icon for type
        CHK(type.SetIconForType("text/plain", iconHelperLarge.Bitmap1(),
                                                        B_LARGE_ICON) == B_OK);
        CHK(type.SetIconForType("text/plain", iconHelperMini.Bitmap1(),
                                                        B_MINI_ICON) == B_OK);
        // app hint
        entry_ref appHintRef;
        CHK(get_ref_for_path("/boot/beos/apps/StyledEdit", &appHintRef) == B_OK);
        CHK(type.SetAppHint(&appHintRef) == B_OK);
        // sniffer rule
        const char *snifferRule = "0.5 [0:0] ('ARGH')";
        CHK(type.SetSnifferRule(snifferRule) == B_OK);
        {
        //   - set icon, preferred app, attr info, file ext., short/long desc.,
        //     icon for, app hint, sniffer rule
                typedef NotificationMessage NM;
                NotificationMessage messages[] = {
                        NM(B_MIME_TYPE_CREATED, testType),
                        NM(B_ICON_CHANGED, testType, true),
                        NM(B_ICON_CHANGED, testType, false),
                        NM(B_PREFERRED_APP_CHANGED, testType),
                        NM(B_ATTR_INFO_CHANGED, testType),
                        NM(B_FILE_EXTENSIONS_CHANGED, testType),
                        NM(B_LONG_DESCRIPTION_CHANGED, testType),
                        NM(B_SHORT_DESCRIPTION_CHANGED, testType),
                        NM(B_ICON_FOR_TYPE_CHANGED, testType, "text/plain", true),
                        NM(B_ICON_FOR_TYPE_CHANGED, testType, "text/plain", false),
                        NM(B_APP_HINT_CHANGED, testType),
                        NM(B_SNIFFER_RULE_CHANGED, testType),
                };
                CheckNotificationMessages(messages, sizeof(messages) / sizeof(NM));
        }

        // set the same values once again
        NextSubTest();
        // icon
        CHK(type.SetIcon(iconHelperLarge.Bitmap1(), B_LARGE_ICON) == B_OK);
        CHK(type.SetIcon(iconHelperMini.Bitmap1(), B_MINI_ICON) == B_OK);
        // preferred app
        CHK(type.SetPreferredApp(testTypeApp) == B_OK);
        // attr info
        CHK(type.SetAttrInfo(&attrInfo) == B_OK);
// file extensions
        CHK(extensions.AddString("extensions", "arg") == B_OK);
        CHK(extensions.AddString("extensions", "ugh") == B_OK);
        CHK(type.SetFileExtensions(&extensions) == B_OK);
        // long/short description
        CHK(type.SetLongDescription("quite short for a long description") == B_OK);
        CHK(type.SetShortDescription("short description") == B_OK);
        // icon for type
        CHK(type.SetIconForType("text/plain", iconHelperLarge.Bitmap1(),
                                                        B_LARGE_ICON) == B_OK);
        CHK(type.SetIconForType("text/plain", iconHelperMini.Bitmap1(),
                                                        B_MINI_ICON) == B_OK);
        // app hint
        CHK(type.SetAppHint(&appHintRef) == B_OK);
        // sniffer rule
        CHK(type.SetSnifferRule(snifferRule) == B_OK);
        {
        //   - set icon, preferred app, attr info, file ext., short/long desc.,
        //     icon for, app hint, sniffer rule
                typedef NotificationMessage NM;
                NotificationMessage messages[] = {
                        NM(B_ICON_CHANGED, testType, true),
                        NM(B_ICON_CHANGED, testType, false),
                        NM(B_PREFERRED_APP_CHANGED, testType),
                        NM(B_ATTR_INFO_CHANGED, testType),
                        NM(B_FILE_EXTENSIONS_CHANGED, testType),
                        NM(B_LONG_DESCRIPTION_CHANGED, testType),
                        NM(B_SHORT_DESCRIPTION_CHANGED, testType),
                        NM(B_ICON_FOR_TYPE_CHANGED, testType, "text/plain", true),
                        NM(B_ICON_FOR_TYPE_CHANGED, testType, "text/plain", false),
                        NM(B_APP_HINT_CHANGED, testType),
                        NM(B_SNIFFER_RULE_CHANGED, testType),
                };
                CheckNotificationMessages(messages, sizeof(messages) / sizeof(NM));
        }

        // set different values
        NextSubTest();
        // icon
        CHK(type.SetIcon(iconHelperLarge.Bitmap2(), B_LARGE_ICON) == B_OK);
        CHK(type.SetIcon(iconHelperMini.Bitmap2(), B_MINI_ICON) == B_OK);
        // preferred app
        CHK(type.SetPreferredApp("application/x-vnd.Be-STEE") == B_OK);
        // attr info
        BMessage attrInfo2;
        FillAttrInfo(attrInfo2, 1);
        CHK(type.SetAttrInfo(&attrInfo2) == B_OK);
        // file extensions
        CHK(extensions.AddString("extensions", "uff") == B_OK);
        CHK(extensions.AddString("extensions", "err") == B_OK);
        CHK(type.SetFileExtensions(&extensions) == B_OK);
        // long/short description
        CHK(type.SetLongDescription("not that short description") == B_OK);
        CHK(type.SetShortDescription("pretty short description") == B_OK);
        // icon for type
        CHK(type.SetIconForType("text/plain", iconHelperLarge.Bitmap2(),
                                                        B_LARGE_ICON) == B_OK);
        CHK(type.SetIconForType("text/plain", NULL,
                                                        B_LARGE_ICON) == B_OK);
        CHK(type.SetIconForType("text/plain", iconHelperMini.Bitmap2(),
                                                        B_MINI_ICON) == B_OK);
        // app hint
        entry_ref appHintRef2;
        CHK(get_ref_for_path("/boot/beos/apps/NetPositive", &appHintRef2) == B_OK);
        CHK(type.SetAppHint(&appHintRef2) == B_OK);
        // sniffer rule
        const char *snifferRule2 = "0.7 [0:5] ('YEAH!')";
        CHK(type.SetSnifferRule(snifferRule2) == B_OK);
        // delete
        CHK(type.Delete() == B_OK);
        {
        //   - set icon, preferred app, attr info, file ext., short/long desc.,
        //     icon for, app hint, sniffer rule
                typedef NotificationMessage NM;
                NotificationMessage messages[] = {
                        NM(B_ICON_CHANGED, testType, true),
                        NM(B_ICON_CHANGED, testType, false),
                        NM(B_PREFERRED_APP_CHANGED, testType),
                        NM(B_ATTR_INFO_CHANGED, testType),
                        NM(B_FILE_EXTENSIONS_CHANGED, testType),
                        NM(B_LONG_DESCRIPTION_CHANGED, testType),
                        NM(B_SHORT_DESCRIPTION_CHANGED, testType),
                        NM(B_ICON_FOR_TYPE_CHANGED, testType, "text/plain", true),
                        NM(B_ICON_FOR_TYPE_CHANGED, testType, "text/plain", true),
                        NM(B_ICON_FOR_TYPE_CHANGED, testType, "text/plain", false),
                        NM(B_APP_HINT_CHANGED, testType),
                        NM(B_SNIFFER_RULE_CHANGED, testType),
                        NM(B_MIME_TYPE_DELETED, testType),
                };
                CheckNotificationMessages(messages, sizeof(messages) / sizeof(NM));
        }

        // StopWatching() and try again -- no messages should be sent anymore
        CHK(BMimeType::StopWatching(target) == B_OK);
        // install
        CHK(type.InitCheck() == B_OK);
        CHK(type.IsInstalled() == false);
        CHK(type.Install() == B_OK);
        // icon
        CHK(type.SetIcon(iconHelperLarge.Bitmap1(), B_LARGE_ICON) == B_OK);
        CHK(type.SetIcon(iconHelperMini.Bitmap1(), B_MINI_ICON) == B_OK);
        // preferred app
        CHK(type.SetPreferredApp(testTypeApp) == B_OK);
        // attr info
        CHK(type.SetAttrInfo(&attrInfo) == B_OK);
        // file extensions
        CHK(extensions.AddString("extensions", "arg") == B_OK);
        CHK(extensions.AddString("extensions", "ugh") == B_OK);
        CHK(type.SetFileExtensions(&extensions) == B_OK);
        // long/short description
        CHK(type.SetLongDescription("quite short for a long description") == B_OK);
        CHK(type.SetShortDescription("short description") == B_OK);
        // icon for type
        CHK(type.SetIconForType("text/plain", iconHelperLarge.Bitmap1(),
                                                        B_LARGE_ICON) == B_OK);
        CHK(type.SetIconForType("text/plain", iconHelperMini.Bitmap1(),
                                                        B_MINI_ICON) == B_OK);
        // app hint
        CHK(type.SetAppHint(&appHintRef) == B_OK);
        // sniffer rule
        CHK(type.SetSnifferRule(snifferRule) == B_OK);
        // delete
        CHK(type.Delete() == B_OK);
        {
                CheckNotificationMessages(NULL, 0);
        }

        // bad args
        // StopWatching() another target
        NextSubTest();
        // install
        CHK(type.InitCheck() == B_OK);
        CHK(type.IsInstalled() == false);
        CHK(type.Install() == B_OK);
        // try to start/stop watching with an invalid target, stop the wrong target
        BMessenger target2(fApplication);
        CHK(target2.IsValid() == true);
        BMessenger target3("application/does-not_exist");
        CHK(target3.IsValid() == false);
// R5: An invalid messenger is fine for any reason?!
#if !TEST_R5
        CHK(BMimeType::StartWatching(target3) == B_BAD_VALUE);
#endif
        CHK(BMimeType::StartWatching(target) == B_OK);
#if !TEST_R5
        CHK(BMimeType::StopWatching(target3) == B_BAD_VALUE);
#endif
        CHK(BMimeType::StopWatching(target2) != B_OK);  // R5 == B_BAD_VALUE, Haiku == B_ENTRY_NOT_FOUND
        CHK(BMimeType::StopWatching(target) == B_OK);
        // delete
        CHK(type.Delete() == B_OK);
}

// CheckNotificationMessage
void
MimeTypeTest::CheckNotificationMessages(const NotificationMessage *messages,
                                                                                int32 count)
{
        // wait for the messages
        snooze(100000);
        if (fApplication) {
                BMessageQueue &queue = fApplication->Handler().Queue();
                CPPUNIT_ASSERT( queue.Lock() );
                try {
                        int32 messageNum = 0;
                        while (BMessage *_message = queue.NextMessage()) {
                                BMessage message(*_message);
                                delete _message;
//printf("\nmessage: %ld\n", messageNum);
//message.PrintToStream();
                                CPPUNIT_ASSERT( messageNum < count );
                                const NotificationMessage &entry = messages[messageNum];
                                CPPUNIT_ASSERT( message.what == B_META_MIME_CHANGED );
                                // which
                                int32 which;
                                CPPUNIT_ASSERT( message.FindInt32("be:which", &which)
                                                                == B_OK );
                                CPPUNIT_ASSERT( entry.which == which );
                                // type
                                const char *type;
                                CPPUNIT_ASSERT( message.FindString("be:type", &type) == B_OK );
                                CPPUNIT_ASSERT( entry.type == type );
                                // extra type
                                const char *extraType;
                                if (entry.hasExtraType) {
                                        CPPUNIT_ASSERT( message.FindString("be:extra_type",
                                                                                                           &extraType) == B_OK);
                                        CPPUNIT_ASSERT( entry.extraType == extraType );
                                } else {
                                        CPPUNIT_ASSERT( message.FindString("be:extra_type",
                                                                                &extraType) == B_NAME_NOT_FOUND);
                                }
                                // large icon
                                bool largeIcon;
                                if (entry.hasLargeIcon) {
                                        CPPUNIT_ASSERT( message.FindBool("be:large_icon",
                                                                                                         &largeIcon) == B_OK);
                                        CPPUNIT_ASSERT( entry.largeIcon == largeIcon );
                                } else {
                                        CPPUNIT_ASSERT( message.FindBool("be:large_icon",
                                                                                &largeIcon) == B_NAME_NOT_FOUND);
                                }
                                messageNum++;
                        }
                        CPPUNIT_ASSERT( messageNum == count );
                } catch (CppUnit::Exception exception) {
                        queue.Unlock();
                        throw exception;
                }
                queue.Unlock();
        }
}

// helper class for update_mime_info() tests
class MimeInfoTestFile {
public:
        MimeInfoTestFile(string name, string type, const void *data = NULL,
                                         int32 size = -1)
                : name(name),
                  type(type),
                  data(NULL),
                  size(0)
        {
                if (data) {
                        if (size == -1)
                                this->size = strlen((const char*)data) + 1;
                        else
                                this->size = size;
                        this->data = new char[this->size];
                        memcpy(this->data, data, this->size);
                }
        }

        ~MimeInfoTestFile()
        {
                delete[] data;
        }

        status_t Create()
        {
                BFile file(name.c_str(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
                status_t error = file.InitCheck();
                if (error == B_OK && data) {
                        ssize_t written = file.Write(data, size);
                        if (written < 0)
                                error = written;
                        else if (written != size)
                                error = B_ERROR;
                }
                return error;
        }

        status_t Delete()
        {
                return BEntry(name.c_str()).Remove();
        }

        string  name;
        string  type;
        char    *data;
        int32   size;
};

// UpdateMimeInfoTest
void
MimeTypeTest::UpdateMimeInfoTest()
{
// Uncomment the following lines to enjoy the quiet time provided
// by a nice, full mime update. :-)

//      cout << "begin..." << endl;
//      CHK(update_mime_info(NULL, true, true, false) == B_OK);
//      cout << "end..." << endl;

        // tests:
        // * update_mime_info()

        // Note:
        // * Only synchronous calls are tested.
        // * Updating all files is not tested as it takes too long.

        // individual files
        execCommand(string("mkdir ") + testDir + "/subdir1 "
                                + testDir + "/subdir2 "
                                + testDir + "/subdir2/subsubdir1");
        MimeInfoTestFile files[] = {
                MimeInfoTestFile(string(testDir) + "/file1.cpp", "text/x-source-code"),
                MimeInfoTestFile(string(testDir) + "/subdir1/file1.gif", "image/gif"),
                MimeInfoTestFile(string(testDir) + "/subdir2/subsubdir1/file1",
                                                 "text/html", "<html>\n<body>\n</body></html>\n")
        };
        int fileCount = sizeof(files) / sizeof(MimeInfoTestFile);
        // synchronous
        for (int32 i = 0; i < fileCount; i++) {
                NextSubTest();
                MimeInfoTestFile &file = files[i];
                // no recursion
                CHK(file.Create() == B_OK);
                CHK(update_mime_info(file.name.c_str(), false, true, false) == B_OK);
                BNode node(file.name.c_str());
                CHK(node.InitCheck() == B_OK);
                BString type;
                CHK(node.ReadAttrString("BEOS:TYPE", &type) == B_OK);
                node.Unset();
                CHK(type == file.type.c_str());
                CHK(file.Delete() == B_OK);
                // recursion
                CHK(file.Create() == B_OK);
                CHK(update_mime_info(file.name.c_str(), true, true, false) == B_OK);
                CHK(node.SetTo(file.name.c_str()) == B_OK);
                type = "";
                CHK(node.ReadAttrString("BEOS:TYPE", &type) == B_OK);
                node.Unset();
                CHK(type == file.type.c_str());
                CHK(file.Delete() == B_OK);
        }

//------------------------------------------------------------------------------
// Asynchronous calls
//------------------------------------------------------------------------------

        const bigtime_t kSnoozeTime = 500000;
        for (int32 i = 0; i < fileCount; i++) {
                NextSubTest();
                MimeInfoTestFile &file = files[i];
                // no recursion
                CHK(file.Create() == B_OK);
                CHK(update_mime_info(file.name.c_str(), false, false, false) == B_OK);
                // give the system some time to do the update asynchronously
                snooze(kSnoozeTime);
                BNode node(file.name.c_str());
                CHK(node.InitCheck() == B_OK);
                BString type;
                CHK(node.ReadAttrString("BEOS:TYPE", &type) == B_OK);
                node.Unset();
                CHK(type == file.type.c_str());
                CHK(file.Delete() == B_OK);
                // recursion
                CHK(file.Create() == B_OK);
                CHK(update_mime_info(file.name.c_str(), true, false, false) == B_OK);
                // give the system some time to do the update asynchronously
                snooze(kSnoozeTime);
                CHK(node.SetTo(file.name.c_str()) == B_OK);
                type = "";
                CHK(node.ReadAttrString("BEOS:TYPE", &type) == B_OK);
                node.Unset();
                CHK(type == file.type.c_str());
                CHK(file.Delete() == B_OK);
        }

// TODO: The BeBook says: "if force is true, files are updated even if they've
// been updated already."
// As I understand this, calling update_mime_info() with force == true on a
// file, should set the BEOS:TYPE attribute regardless of whether it already
// had a value. The following test shows, that BEOS:TYPE remains unchanged
// though.
#if TEST_OBOS
        for (int32 i = 0; i < fileCount; i++) {
                MimeInfoTestFile &file = files[i];
//printf("file: %s\n", file.name.c_str());
                CHK(file.Create() == B_OK);
                // add a type attribute
                BNode node(file.name.c_str());
                CHK(node.InitCheck() == B_OK);
                BString type("text/plain");
                CHK(node.WriteAttrString("BEOS:TYPE", &type) == B_OK);
                // update, force == false
                CHK(update_mime_info(file.name.c_str(), false, true, false) == B_OK);
                type = "";
                CHK(node.ReadAttrString("BEOS:TYPE", &type) == B_OK);
                CHK(type == "text/plain");
                // update, force == true
                CHK(update_mime_info(file.name.c_str(), false, true, true) == B_OK);
                type = "";
                CHK(node.ReadAttrString("BEOS:TYPE", &type) == B_OK);
                node.Unset();
//printf("%s <-> %s\n", type.String(), file.type.c_str());
                CHK(type == file.type.c_str());
                CHK(file.Delete() == B_OK);
        }
#endif  // TEST_OBOS

        // directory
        NextSubTest();
        // create
        for (int32 i = 0; i < fileCount; i++) {
                MimeInfoTestFile &file = files[i];
                CHK(file.Create() == B_OK);
        }
        // update, not recursive
        CHK(update_mime_info(testDir, false, true, false) == B_OK);
        // check
        for (int32 i = 0; i < fileCount; i++) {
                MimeInfoTestFile &file = files[i];
                BNode node(file.name.c_str());
                CHK(node.InitCheck() == B_OK);
                BString type;
                CHK(node.ReadAttrString("BEOS:TYPE", &type) == B_ENTRY_NOT_FOUND);
        }
        // delete, re-create
        for (int32 i = 0; i < fileCount; i++) {
                MimeInfoTestFile &file = files[i];
                CHK(file.Delete() == B_OK);
                CHK(file.Create() == B_OK);
        }
        // update, recursive
        CHK(update_mime_info(testDir, true, true, false) == B_OK);
        for (int32 i = 0; i < fileCount; i++) {
                MimeInfoTestFile &file = files[i];
                BNode node(file.name.c_str());
                CHK(node.InitCheck() == B_OK);
                BString type;
                CHK(node.ReadAttrString("BEOS:TYPE", &type) == B_OK);
                node.Unset();
                CHK(type == file.type.c_str());
        }
        // delete
        for (int32 i = 0; i < fileCount; i++) {
                MimeInfoTestFile &file = files[i];
                CHK(file.Delete() == B_OK);
        }

        // bad args: non-existing file
        NextSubTest();
        BEntry entry(files[0].name.c_str());
        CHK(entry.InitCheck() == B_OK);
        CHK(entry.Exists() == false);
//#if TEST_R5
        CHK(update_mime_info(files[0].name.c_str(), false, true, false) == B_OK);
//#else
//      CHK(update_mime_info(files[0].name.c_str(), false, true, false) == B_ENTRY_NOT_FOUND);
//#endif
}

// WriteStringAttr
static
status_t
WriteStringAttr(BNode &node, string name, string _value)
{
        // Wrapper for BNode::WriteAttrString() taking string rather than
        // const char*/BString* parameters.
        BString value(_value.c_str());
        return node.WriteAttrString(name.c_str(), &value);
}

const uint32 MINI_ICON_TYPE = 'MICN';
const uint32 LARGE_ICON_TYPE = 'ICON';

// helper class for create_app_meta_mime() tests
class AppMimeTestFile {
public:
        AppMimeTestFile(string name, string type, string signature,
                                        string snifferRule,
                                        const void *miniIcon = NULL, const void *largeIcon = NULL)
                : name(name),
                  type(type),
                  signature(signature),
                  snifferRule(snifferRule),
                  miniIcon(NULL),
                  largeIcon(NULL)
        {
                SetMiniIcon(miniIcon);
                SetLargeIcon(largeIcon);
        }

        ~AppMimeTestFile()
        {
                SetMiniIcon(NULL);
                SetLargeIcon(NULL);
        }

        void SetMiniIcon(const void *icon)
        {
                if (miniIcon) {
                        delete[] miniIcon;
                        miniIcon = NULL;
                }
                if (icon) {
                        miniIcon = new char[256];
                        memcpy(miniIcon, icon, 256);
                }
        }

        void SetLargeIcon(const void *icon)
        {
                if (largeIcon) {
                        delete[] largeIcon;
                        largeIcon = NULL;
                }
                if (icon) {
                        largeIcon = new char[1024];
                        memcpy(largeIcon, icon, 1024);
                }
        }

        status_t Create(bool setAttributes, bool setResources)
        {
                BFile file(name.c_str(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
                status_t error = file.InitCheck();
                // attributes
                if (error == B_OK && setAttributes) {
                        // type
                        if (type.length() > 0)
                                error = WriteStringAttr(file, "BEOS:TYPE", type);
                        // signature
                        if (error == B_OK)
                                error = WriteStringAttr(file, "BEOS:APP_SIG", signature);
                        // sniffer rule
                        if (error == B_OK)
                                error = WriteStringAttr(file, "BEOS:SNIFF_RULE", snifferRule);
                        // mini icon
                        if (error == B_OK && miniIcon) {
                                ssize_t written = file.WriteAttr("BEOS:M:STD_ICON",
                                                                                                 MINI_ICON_TYPE, 0, miniIcon,
                                                                                                 256);
                                if (written < 0)
                                        error = written;
                                else if (written != 256)
                                        error = B_ERROR;
                        }
                        // large icon (ignored)
                        if (error == B_OK && largeIcon) {
                                ssize_t written = file.WriteAttr("META:L:STD_ICON",
                                                                                                 LARGE_ICON_TYPE, 0, largeIcon,
                                                                                                 1024);
                                if (written < 0)
                                        error = written;
                                else if (written != 1024)
                                        error = B_ERROR;
                        }
                }
                // resources
                if (error == B_OK && setResources) {
                        BResources resources;
                        error = resources.SetTo(&file, true);
                        // type (ignored)
                        if (error == B_OK && type.length() > 0) {
                                error = resources.AddResource(B_STRING_TYPE, 2, type.c_str(),
                                                                                          type.length() + 1, "BEOS:TYPE");
                        }
                        // signature (ignored)
                        if (error == B_OK) {
                                error = resources.AddResource(B_STRING_TYPE, 1,
                                                                                          signature.c_str(),
                                                                                          signature.length() + 1,
                                                                                          "BEOS:APP_SIG");
                        }
                        // mini icon (ignored)
                        if (error == B_OK && miniIcon) {
                                error = resources.AddResource(MINI_ICON_TYPE, 101, miniIcon,
                                                                                          256, "BEOS:M:STD_ICON");
                        }
                        // file types (ignored)
                        if (error == B_OK) {
                                BMessage msg;
                                char *buffer = NULL;
                                error = msg.AddString("types", "text/x-email");
                                if (!error)
                                        error = msg.AddString("types", "video/mpeg");
                                if (!error) {
                                        buffer = new char[msg.FlattenedSize()];
                                        if (!buffer)
                                                error = B_NO_MEMORY;
                                }
                                if (!error)
                                        error = msg.Flatten(buffer, msg.FlattenedSize());
                                if (!error)
                                        error = resources.AddResource(B_MESSAGE_TYPE, 1, buffer,
                                                                                                        msg.FlattenedSize(), "BEOS:FILE_TYPES");
                                delete [] buffer;
                        }
                }
                return error;
        }

        status_t Delete(bool deleteMimeType)
        {
                status_t error = BEntry(name.c_str()).Remove();
                if (error == B_OK && deleteMimeType) {
                        BMimeType type;
                        // the type need not necessarily exist
                        error = type.SetTo(signature.c_str());
                        if (error == B_OK && deleteMimeType && type.IsInstalled())
                                error = type.Delete();
                }
                return error;
        }

        string  name;
        string  type;
        string  signature;
        string  snifferRule;
        char    *miniIcon;
        char    *largeIcon;
};

// CheckAppMetaMime
static
void
CheckAppMetaMime(AppMimeTestFile &file)
{
        BMimeType type;
        CHK(type.SetTo(file.signature.c_str()) == B_OK);
        CHK(type.IsInstalled() == true);
        // short description
        char shortDescription[B_MIME_TYPE_LENGTH + 1];
        CHK(type.GetShortDescription(shortDescription) == B_OK);
        BPath path(file.name.c_str(), NULL, true);
        CHK(string(path.Leaf()) == shortDescription);
        // preferred app
        char preferredApp[B_MIME_TYPE_LENGTH + 1];
        CHK(type.GetPreferredApp(preferredApp) == B_OK);
        CHK(file.signature == preferredApp);
        // META:PPATH
        BNode typeFile;
        string typeFilename(string(mimeDatabaseDir) + "/" + file.signature);
//      cout << "typeFilename == '" << typeFilename << "'" << endl;
        CHK(typeFile.SetTo(typeFilename.c_str()) == B_OK);
        char filePath[B_PATH_NAME_LENGTH + 1];
        CHK(typeFile.ReadAttr("META:PPATH", B_STRING_TYPE, 0, filePath,
                                                  B_PATH_NAME_LENGTH + 1) > 0);
        CHK(path == filePath);
        // mini icon
        if (file.miniIcon) {
                BBitmap icon(BRect(0, 0, 15, 15), B_CMAP8);
                CHK(type.GetIcon(&icon, B_MINI_ICON) == B_OK);
                CHK(memcmp(icon.Bits(), file.miniIcon, 256) == 0);
        }
        // large icon
/*      if (file.miniIcon) {
                BBitmap icon(BRect(0, 0, 31, 31), B_CMAP8);
                CHK(type.GetIcon(&icon, B_LARGE_ICON) == B_OK);
                CHK(memcmp(icon.Bits(), file.largeIcon, 1024) == 0);
        }*/
}

// CreateAppMetaMimeTest
void
MimeTypeTest::CreateAppMetaMimeTest()
{

// Uncomment the following lines to enjoy the quiet time provided by
// a nice, full create_app_meta_mime() update. :-)

//      cout << "begin..." << endl;
//      CHK(create_app_meta_mime(NULL, true, true, false) == B_OK);
//      cout << "end" << endl;

        // tests:
        // * create_app_meta_mime()

        // Note:
        // * Only synchronous calls are tested.
        // * The recursive flag isn't tested -- the BeBook sais, it is unused.
        // * Updating all apps is not tested as it takes too long.

        // Create a couple of icons to play around with
        char miniIcon1[256];
        char miniIcon2[256];
        for (int ch = 0; ch < 256; ch++) {
                miniIcon1[ch] = ch;
                miniIcon2[ch] = 255-ch;
        }
        char largeIcon1[1024];
        char largeIcon2[1024];
        for (int i = 0; i < 1024; i++) {
                char ch = i % 256;
                largeIcon1[i] = ch;
                largeIcon2[i] = 255-ch;
        }

        // attributes and resources
        NextSubTest();
        execCommand(string("mkdir ") + testDir + "/subdir1 "
                                + testDir + "/subdir2 "
                                + testDir + "/subdir2/subsubdir1");
        AppMimeTestFile files[] = {
                // AppMimeTestFile(name, type, sig, rule, miniIcon, largeIcon)
                AppMimeTestFile(string(testDir) + "/file1",
                                                "",
                                                "application/x-vnd.obos.mime.test.test1",
                                                "0.0 ('abc')",
                                                miniIcon1,
                                                NULL),
                AppMimeTestFile(string(testDir) + "/file2",
                                                "text/x-source-code",
                                                "application/x-vnd.obos.mime.test.test2",
                                                "0.0 ('xyz')",
                                                miniIcon2,
                                                largeIcon2),
                AppMimeTestFile(string(testDir) + "/file3",
                                                "application/x-vnd.Be-elfexecutable",
                                                "application/x-vnd.obos.mime.test.test3",
                                                "0.0 ('rst')",
                                                NULL,
                                                largeIcon1),
        };
        const int fileCount = sizeof(files) / sizeof(AppMimeTestFile);
//------------------------------------------------------------------------------
// Synchronous calls
//------------------------------------------------------------------------------
        for (int32 i = 0; i < fileCount; i++) {
                NextSubTest();
                // create file, create_app_meta_mime()
                AppMimeTestFile &file = files[i];
                CHK(file.Create(true, true) == B_OK);
                CHK(create_app_meta_mime(file.name.c_str(), false, true, false)
                        == B_OK);
                // check the MIME type
                CheckAppMetaMime(file);
                // clean up
                CHK(file.Delete(true) == B_OK);
        }
//      snooze(999000000);

        // attributes only
        for (int32 i = 0; i < fileCount; i++) {
                NextSubTest();
                // create file, create_app_meta_mime()
                AppMimeTestFile &file = files[i];
                CHK(file.Create(true, false) == B_OK);
                CHK(create_app_meta_mime(file.name.c_str(), false, true, false)
                        == B_OK);
                // check the MIME type
                CheckAppMetaMime(file);
                // clean up
                CHK(file.Delete(true) == B_OK);
        }

        // resources only
        for (int32 i = 0; i < fileCount; i++) {
                NextSubTest();
                // create file, create_app_meta_mime()
                AppMimeTestFile &file = files[i];
                CHK(file.Create(false, true) == B_OK);
                CHK(create_app_meta_mime(file.name.c_str(), false, true, false)
                        == B_OK);
                // check the MIME type
                BMimeType type;
                CHK(type.SetTo(file.signature.c_str()) == B_OK);
                CHK(type.IsInstalled() == false);
                // clean up
                CHK(file.Delete(false) == B_OK);
        }

//------------------------------------------------------------------------------
// Asynchronous calls
//------------------------------------------------------------------------------
        const bigtime_t kSnoozeTime = 500000;
        for (int32 i = 0; i < fileCount; i++) {
                NextSubTest();
                // create file, create_app_meta_mime()
                AppMimeTestFile &file = files[i];
                CHK(file.Create(true, true) == B_OK);
                CHK(create_app_meta_mime(file.name.c_str(), false, false, false)
                        == B_OK);
                // give the system some time to do the update asynchronously
                snooze(kSnoozeTime);
                // check the MIME type
                CheckAppMetaMime(file);
                // clean up
                CHK(file.Delete(true) == B_OK);
        }

        // attributes only
        for (int32 i = 0; i < fileCount; i++) {
                NextSubTest();
                // create file, create_app_meta_mime()
                AppMimeTestFile &file = files[i];
                CHK(file.Create(true, false) == B_OK);
                CHK(create_app_meta_mime(file.name.c_str(), false, false, false)
                        == B_OK);
                // give the system some time to do the update asynchronously
                snooze(kSnoozeTime);
                // check the MIME type
                CheckAppMetaMime(file);
                // clean up
                CHK(file.Delete(true) == B_OK);
        }

        // resources only
        for (int32 i = 0; i < fileCount; i++) {
                NextSubTest();
                // create file, create_app_meta_mime()
                AppMimeTestFile &file = files[i];
                CHK(file.Create(false, true) == B_OK);
                CHK(create_app_meta_mime(file.name.c_str(), false, false, false)
                        == B_OK);
                // give the system some time to do the update asynchronously
                snooze(kSnoozeTime);
                BMimeType type;
                CHK(type.SetTo(file.signature.c_str()) == B_OK);
                CHK(type.IsInstalled() == false);
                // clean up
                CHK(file.Delete(false) == B_OK);
        }


        // test the force flag
// TODO: The BeBook says: "If force is true, entries are created even if they
// already exist."
// As I understand this, re-calling create_app_meta_mime() with force == true,
// after modifying the original file (e.g. the mini icon attribute) or
// calling it on another file with the same signature, should update the
// database entry. But the following tests show, that this doesn't happen.
// They fail in the third CheckAppMetaMime().
#if !TEST_R5
        // same file, same signature, other parameters
        {
                char icon1[256];
                char icon2[256];
                memset(icon1, 1, 256);
                memset(icon2, 2, 256);
                AppMimeTestFile file1(string(testDir) + "/file1",
                                                "application/x-vnd.Be-elfexecutable",
                                                "application/x-vnd.obos.mime.test.test1",
                                                icon1);
                AppMimeTestFile file2(string(testDir) + "/file1",
                                                "application/x-vnd.Be-elfexecutable",
                                                "application/x-vnd.obos.mime.test.test1",
                                                icon2);
                // create file 1, create_app_meta_mime()
                CHK(file1.Create(true, true) == B_OK);
                CHK(create_app_meta_mime(file1.name.c_str(), false, true, false)
                        == B_OK);
                // check the MIME type
                CheckAppMetaMime(file1);
                // create file 2, create_app_meta_mime(), no force
                CHK(file2.Create(true, true) == B_OK);
                CHK(create_app_meta_mime(file2.name.c_str(), false, true, false)
                        == B_OK);
                // check the MIME type
                CheckAppMetaMime(file1);
                // create_app_meta_mime(), force
                CHK(create_app_meta_mime(file2.name.c_str(), false, true, true)
                        == B_OK);
                // check the MIME type
                CheckAppMetaMime(file2);
                // clean up
                CHK(file2.Delete(true) == B_OK);
        }
        // different file, same signature, other parameters
        {
                char icon1[256];
                char icon2[256];
                memset(icon1, 1, 256);
                memset(icon2, 2, 256);
                AppMimeTestFile file1(string(testDir) + "/file1",
                                                "application/x-vnd.Be-elfexecutable",
                                                "application/x-vnd.obos.mime.test.test1",
                                                icon1);
                AppMimeTestFile file2(string(testDir) + "/file2",
                                                "application/x-vnd.Be-elfexecutable",
                                                "application/x-vnd.obos.mime.test.test1",
                                                icon2);
                // create file 1, create_app_meta_mime()
                CHK(file1.Create(true, true) == B_OK);
                CHK(create_app_meta_mime(file1.name.c_str(), false, true, false)
                        == B_OK);
                // check the MIME type
                CheckAppMetaMime(file1);
                // create file 2, create_app_meta_mime(), no force
                CHK(file2.Create(true, true) == B_OK);
                CHK(create_app_meta_mime(file2.name.c_str(), false, true, false)
                        == B_OK);
                // check the MIME type
                CheckAppMetaMime(file1);
                // create_app_meta_mime(), force
                CHK(create_app_meta_mime(file2.name.c_str(), false, true, true)
                        == B_OK);
                // check the MIME type
                CheckAppMetaMime(file2);
                // clean up
                CHK(file1.Delete(true) == B_OK);
                CHK(file2.Delete(true) == B_OK);
        }
#endif  // !TEST_R5

        // bad args
        NextSubTest();
        // no signature
        CHK(files[0].Create(false, false) == B_OK);
        CHK(create_app_meta_mime(files[0].name.c_str(), false, true, false)
                == B_OK);
        CHK(files[0].Delete(false) == B_OK);
        // non-existing file
        CHK(create_app_meta_mime(files[0].name.c_str(), false, true, false)
                == B_OK);

}

// CheckIconData
static
void
CheckIconData(const char *device, int32 iconSize, const void* data)
{
        // open the device
        int fd = open(device, O_RDONLY);
        CHK(fd != -1);
        // get the icon
        char buffer[1024];
        device_icon iconData = {
                iconSize,
                buffer
        };
        int error = ioctl(fd, B_GET_ICON, &iconData);
        // close the device
        CHK(close(fd) == 0);
        CHK(error == 0);
        // compare the icon data
        CHK(memcmp(data, buffer, iconSize * iconSize) == 0);
}

// GetDeviceIconTest
void
MimeTypeTest::GetDeviceIconTest()
{
        // tests:
        // * get_device_icon()

        // test a volume device, a non-volume device, and an invalid dev name
        struct test_case {
                const char      *path;
                bool            valid;
        } testCases[] = {
                { "/dev/zero", false },
                { "/boot", true },
                { "/boot/home", false }
        };
        const int testCaseCount = sizeof(testCases) / sizeof(test_case);
        for (int32 i = 0; i < testCaseCount; i++) {
                NextSubTest();
                test_case &testCase = testCases[i];
                // get device name from path name
                fs_info info;
                const char *deviceName = testCase.path;
                if (testCase.valid) {
                        dev_t dev = dev_for_path(testCase.path);
                        CHK(dev > 0);
                        CHK(fs_stat_dev(dev, &info) == 0);
                        deviceName = info.device_name;
                }
                // the two valid and one invalid icon size
                const int32     iconSizes[] = { 16, 32, 20 };
                const bool      validSizes[] = { true, true, false };
                const int sizeCount = sizeof(iconSizes) / sizeof(int32);
                for (int32 k = 0; k < sizeCount; k++) {
                        int32 size = iconSizes[k];
                        bool valid = testCase.valid && validSizes[k];
                        char buffer[1024];
                        if (valid) {
                                CHK(get_device_icon(deviceName, buffer, size) == B_OK);
                                CheckIconData(deviceName, size, buffer);
                                // bad args: NULL buffer
// R5: Wanna see KDL? Here you go...
#if !TEST_R5
                                CHK(get_device_icon(deviceName, NULL, size) == B_BAD_VALUE);
#endif
                        } else
                                CHK(get_device_icon(deviceName, buffer, size) != B_OK);
                }
        }
}

// SnifferRuleTest
void
MimeTypeTest::SnifferRuleTest()
{
        // tests:
        // * status_t GetSnifferRule(BString *result) const;
        // * status_t SetSnifferRule(const char *);
        // * static status_t CheckSnifferRule(const char *rule, BString *parseError);

        // test a couple of valid and invalid rules
        struct test_case {
                const char      *rule;
                const char      *error; // NULL, if valid
        } testCases[] = {
                // valid rules
                { "1.0 (\"ABCD\")", NULL },
                { "1.0 ('ABCD')", NULL },
                { "  1.0 ('ABCD')  ", NULL },
                { "0.8 [0:3] ('ABCDEFG' | 'abcdefghij')", NULL },
                { "0.5([10]'ABCD'|[17]'abcd'|[13]'EFGH')", NULL } ,
                { "0.5  \n   [0:3]  \t ('ABCD' \n | 'abcd' | 'EFGH')", NULL },
                { "0.8 [  0  :  3  ] ('ABCDEFG' | 'abcdefghij')", NULL },
                { "0.8 [0:3] ('ABCDEFG' & 'abcdefg')", NULL },
// These two rules are accepted by the R5 sniffer checker, but not
// by the parser. Thus, we're not accepting them with either.
#if TEST_R5
                { "1.0 ('ABCD') | ('EFGH')", NULL },
                { "1.0 [0:3] ('ABCD') | [2:4] ('EFGH')", NULL },
#else
                { "1.0 ('ABCD') | ('EFGH')", "Sniffer pattern error: missing pattern" },
                { "1.0 [0:3] ('ABCD') | [2:4] ('EFGH')", "Sniffer pattern error: missing pattern" },
#endif
                { "0.8 [0:3] (\\077Mkl0x34 & 'abcdefgh')", NULL },
                { "0.8 [0:3] (\\077034 & 'abcd')", NULL },
                { "0.8 [0:3] (\\077\\034 & 'ab')", NULL },
                { "0.8 [0:3] (\\77\\034 & 'ab')", NULL },
                { "0.8 [0:3] (\\7 & 'a')", NULL },
                { "0.8 [0:3] (\"\\17\" & 'a')", NULL },
                { "0.8 [0:3] ('\\17' & 'a')", NULL },
                { "0.8 [0:3] (\\g & 'a')", NULL },
                { "0.8 [0:3] (\\g&\\b)", NULL },
                { "0.8 [0:3] (\\g\\&b & 'abc')", NULL },
                { "0.8 [0:3] (0x3457 & 'ab')", NULL },
                { "0.8 [0:3] (0xA4b7 & 'ab')", NULL },
                { "0.8 [0:3] ('ab\"' & 'abc')", NULL },
                { "0.8 [0:3] (\"ab\\\"\" & 'abc')", NULL },
                { "0.8 [0:3] (\"ab\\A\" & 'abc')", NULL },
                { "0.8 [0:3] (\"ab'\" & 'abc')", NULL },
                { "0.8 [0:3] (\"ab\\\\\" & 'abc')", NULL },
                { "0.8 [-5:-3] (\"abc\" & 'abc')", NULL },
// Also accepted by the R5 sniffer but not the R5 parser. We reject.
#if TEST_R5
                { "0.8 [5:3] (\"abc\" & 'abc')", NULL },
#else
                { "0.8 [5:3] (\"abc\" & 'abc')", "Sniffer Parser Error -- Invalid range: [5:3]" },
#endif
                { "1.0 ('ABCD')", NULL },
                { ".2 ('ABCD')", NULL },
                { "0. ('ABCD')", NULL },
                { "1 ('ABCD')", NULL },
                { "+1 ('ABCD')", NULL },
// We accept extended notation floating point numbers now, but
// not invalid priorities. Thus our checker chokes on these rules,
// whilest R5's does not
#if TEST_R5
                { "1E25 ('ABCD')", NULL },
                { "1e25 ('ABCD')", NULL },
#else
                { "1E25 ('ABCD')", "Sniffer pattern error: invalid priority" },
                { "1e25 ('ABCD')", "Sniffer pattern error: invalid priority" },
#endif

// R5 chokes on this rule :-( Why? I don't know. :-)
#if TEST_R5
                { "1e-3 ('ABCD')", "Sniffer pattern error: missing pattern" },
#else
                { "1e-3 ('ABCD')", NULL },
#endif
                { "+.003e2 ('ABCD')", NULL },
// R5 chokes on this one too. See how much better our checker/parser is? ;-)
#if TEST_R5
                { "-123e-9999999999 ('ABCD')", "Sniffer pattern error: bad token" },    // Hooray for the stunning accuracy of floating point :-)
#else
                { "-123e-9999999999 ('ABCD')", NULL },  // Hooray for the stunning accuracy of floating point :-)
#endif
                // invalid rules
                { "0.0 ('')",
                        "Sniffer pattern error: illegal empty pattern" },
                { "('ABCD')",
                        "Sniffer pattern error: match level expected" },
                { "[0:3] ('ABCD')",
                        "Sniffer pattern error: match level expected" },
                { "0.8 [0:3] ( | 'abcdefghij')",
                  "Sniffer pattern error: missing pattern" },
                { "0.8 [0:3] ('ABCDEFG' | )",
                  "Sniffer pattern error: missing pattern" },
                { "[0:3] ('ABCD')",
                        "Sniffer pattern error: match level expected" },
                { "1.0 (ABCD')",
#if TEST_R5
                        "Sniffer pattern error: misplaced single quote"
#else
                        "Sniffer pattern error: invalid character 'A'"
#endif
                },
                { "1.0 ('ABCD)",
#if TEST_R5
                        "Sniffer pattern error: unterminated rule"
#else
                        "Sniffer pattern error: unterminated single-quoted string"
#endif
                },
                { "1.0 (ABCD)",
#if TEST_R5
                        "Sniffer pattern error: missing pattern"
#else
                        "Sniffer pattern error: invalid character 'A'"
#endif
                },
                { "1.0 (ABCD 'ABCD')",
#if TEST_R5
                        "Sniffer pattern error: missing pattern"
#else
                        "Sniffer pattern error: invalid character 'A'"
#endif
                },
                { "1.0 'ABCD')",
#if TEST_R5
                        "Sniffer pattern error: missing pattern"
#else
                        "Sniffer pattern error: missing pattern"
#endif
                },
                { "1.0 ('ABCD'",
                        "Sniffer pattern error: unterminated rule" },
                { "1.0 'ABCD'",
#if TEST_R5
                        "Sniffer pattern error: missing sniff pattern"
#else
                        "Sniffer pattern error: missing pattern"
#endif
                },
                { "0.5 [0:3] ('ABCD' | 'abcd' | [13] 'EFGH')",
                        "Sniffer pattern error: missing pattern" },
                { "0.5('ABCD'|'abcd'|[13]'EFGH')",
                        "Sniffer pattern error: missing pattern" },
                { "0.5[0:3]([10]'ABCD'|[17]'abcd'|[13]'EFGH')",
                        "Sniffer pattern error: missing pattern" },
                { "0.8 [0x10:3] ('ABCDEFG' | 'abcdefghij')",
                        "Sniffer pattern error: pattern offset expected" },
                { "0.8 [0:A] ('ABCDEFG' | 'abcdefghij')",
#if TEST_R5
                        "Sniffer pattern error: pattern range end expected"
#else
                        "Sniffer pattern error: invalid character 'A'"
#endif
                },
                { "0.8 [0:3] ('ABCDEFG' & 'abcdefghij')",
                        "Sniffer pattern error: pattern and mask lengths do not match" },
                { "0.8 [0:3] ('ABCDEFG' & 'abcdefg' & 'xyzwmno')",
#if TEST_R5
                        "Sniffer pattern error: unterminated rule"
#else
                        "Sniffer pattern error: expecting '|', ')', or possibly '&'"
#endif
                },
                { "0.8 [0:3] (\\g&b & 'a')",
#if TEST_R5
                        "Sniffer pattern error: missing mask"
#else
                        "Sniffer pattern error: invalid character 'b'"
#endif
                },
                { "0.8 [0:3] (\\19 & 'a')",
                        "Sniffer pattern error: pattern and mask lengths do not match" },
                { "0.8 [0:3] (0x345 & 'ab')",
                        "Sniffer pattern error: bad hex literal" },
                { "0.8 [0:3] (0x3457M & 'abc')",
#if TEST_R5
                        "Sniffer pattern error: expecting '|' or '&'"
#else
                        "Sniffer pattern error: invalid character 'M'"
#endif
                },
                { "0.8 [0:3] (0x3457\\7 & 'abc')",
#if TEST_R5
                        "Sniffer pattern error: expecting '|' or '&'"
#else
                        "Sniffer pattern error: expecting '|', ')', or possibly '&'"
#endif
                },

                // Miscellaneous tests designed to hit every remaining
                // relevant "throw new Err()" statement in our scanner.
                // R5 versions may come later, but I don't really see any
                // good reason why at this point...
#if !TEST_R5
                { "\x03  ", "Sniffer pattern error: invalid character '\x03'" },
                { "\"blah", "Sniffer pattern error: unterminated double-quoted string" },
                { "0xThisIsNotAHexCode", "Sniffer pattern error: incomplete hex code" },
                { "0xAndNeitherIsThis:-)", "Sniffer pattern error: bad hex literal" },
                { ".NotAFloat", "Sniffer pattern error: incomplete floating point number" },
                { "-NotANumber", "Sniffer pattern error: incomplete signed number" },
                { "+NotANumber", "Sniffer pattern error: incomplete signed number" },

                { "0.0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "1.0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { ".0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "1e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "-1e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "+1e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "-1.e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "+1.e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "-1.0e", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "+1.0e", "Sniffer pattern error: incomplete extended-notation floating point number" },

                { "0.0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "1.0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { ".0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "1e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "-1e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "+1e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "-1.e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "+1.e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "-1.0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "+1.0e-", "Sniffer pattern error: incomplete extended-notation floating point number" },

                { "0.0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "1.0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { ".0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "1e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "-1e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "+1e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "-1.e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "+1.e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "-1.0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },
                { "+1.0e+", "Sniffer pattern error: incomplete extended-notation floating point number" },

                { "\\11\\", "Sniffer pattern error: incomplete escape sequence" },
                { "\"Escape!! \\", "Sniffer pattern error: incomplete escape sequence" },
                { "'Escape!! \\", "Sniffer pattern error: incomplete escape sequence" },

                { "\\x", "Sniffer pattern error: incomplete escaped hex code" },
                { "\\xNotAHexCode", "Sniffer pattern error: incomplete escaped hex code" },
                { "\\xAlsoNotAHexCode", "Sniffer pattern error: incomplete escaped hex code" },
                { "\\x0", "Sniffer pattern error: incomplete escaped hex code" },

                { "1.0 (\\377)", NULL },
                { "\\400", "Sniffer pattern error: invalid octal literal (octals must be between octal 0 and octal 377 inclusive)" },
                { "\\777", "Sniffer pattern error: invalid octal literal (octals must be between octal 0 and octal 377 inclusive)" },
                { "1.0 (\\800)", NULL },

                { NULL, "Sniffer pattern error: NULL pattern" },

                { "-2", "Sniffer pattern error: invalid priority" },
                { "+2", "Sniffer pattern error: invalid priority" },

                { "1.0", "Sniffer pattern error: missing expression" },
#endif  // !TEST_R5

//! \todo Our parser chokes on this rule and I have no idea why
// I don't currently understand what's wrong with the following rule...
// R5 rejects it though, for whatever reason.
#if TEST_R5
                { "1E-25 ('ABCD')", "Sniffer pattern error: missing pattern" },
#else
//              { "1E-25 ('ABCD')", NULL },
#endif
        };

        const int testCaseCount = sizeof(testCases) / sizeof(test_case);
        BMimeType type;
        CHK(type.SetTo(testType) == B_OK);
        CHK(type.Install() == B_OK);
        for (int32 i = 0; i < testCaseCount; i++) {
                NextSubTest();
                test_case &testCase = testCases[i];
                BString parseError;
                status_t error = BMimeType::CheckSnifferRule(testCase.rule,
                                                                                                         &parseError);
//              printf("\n---------------------\n");
//              printf("rule == '%s', %s\n", testCase.rule, (testCase.error ? "should not pass" : "should pass"));
                if (testCase.error == NULL) {
if (error != B_OK)
printf("\nerror:\n%s\n", parseError.String());
                        CHK(error == B_OK);
                        CHK(type.SetSnifferRule(testCase.rule) == B_OK);
                        BString rule;
                        CHK(type.GetSnifferRule(&rule) == B_OK);
                        CHK(rule == testCase.rule);
                } else {
//                      printf("error == 0x%lx\n", error);
//                      if (parseError.FindLast(testCase.error) < 0) {
//                              printf("\nexpected:\n%s\n", testCase.error);
//                              printf("\nfound:\n%s\n", parseError.String());
//                      }
                        CHK(error == (testCase.rule ? B_BAD_MIME_SNIFFER_RULE : B_BAD_VALUE));
                        CHK(parseError.FindLast(testCase.error) >= 0);

// R5 treats a NULL rule string as an error, and thus R5::SetSnifferRule(NULL) fails.
// We also treat a NULL rule string as an error, but OBOS::SetSnifferRule(NULL) does
// not fail, as all OBOS::BMimeType::Set*(NULL) calls are equivalent to the
// corresponding OBOS::BMimeType::Delete*() calls.
#if TEST_R5
                        CHK(type.SetSnifferRule(testCase.rule) == B_BAD_MIME_SNIFFER_RULE);
#else
                        if (testCase.rule)
                                CHK(type.SetSnifferRule(testCase.rule) == B_BAD_MIME_SNIFFER_RULE);
                        else
                                CHK(type.SetSnifferRule(testCase.rule) == B_OK);
#endif
                }
        }

        // bad args: NULL rule/result string
        NextSubTest();
        BString parseError;
        CHK(BMimeType::CheckSnifferRule("0.0 ('')", NULL)
                == B_BAD_MIME_SNIFFER_RULE);
// R5: crashes when passing a NULL rule/result buffer.
#if !TEST_R5
        CHK(BMimeType::CheckSnifferRule(NULL, &parseError) == B_BAD_VALUE);
        CHK(BMimeType::CheckSnifferRule(NULL, NULL) == B_BAD_VALUE);
        CHK(type.GetSnifferRule(NULL) == B_BAD_VALUE);
#endif

        BString rule;

        // NULL rule to SetSnifferRule unsets the attribute
        NextSubTest();
#if TEST_R5
        CHK(type.IsInstalled() == true);
        CHK(type.SetSnifferRule(NULL) == B_OK);
        CHK(type.GetSnifferRule(&rule) == B_ENTRY_NOT_FOUND);
#else
        CHK(type.IsInstalled() == true);
        if (type.GetSnifferRule(&rule) == B_ENTRY_NOT_FOUND)
                CHK(type.SetSnifferRule("0.0 ('abc')") == B_OK);
        CHK(type.GetSnifferRule(&rule) == B_OK);
        CHK(type.SetSnifferRule(NULL) == B_OK);
        CHK(type.GetSnifferRule(&rule) == B_ENTRY_NOT_FOUND);
        CHK(type.SetSnifferRule(NULL) == B_ENTRY_NOT_FOUND);
#endif

        // bad args: uninstalled type
        CHK(type.Delete() == B_OK);
        CHK(type.GetSnifferRule(&rule) == B_ENTRY_NOT_FOUND);
        CHK(type.SetSnifferRule("0.0 ('ABC')") == B_OK);
#if TEST_R5
        CHK(type.GetSnifferRule(&rule) == B_ENTRY_NOT_FOUND);
#else
        CHK(type.GetSnifferRule(&rule) == B_OK);
#endif

        // bad args: uninitialized BMimeType
        type.Unset();
        CHK(type.GetSnifferRule(&rule) != B_OK);
        CHK(type.SetSnifferRule("0.0 ('ABC')") != B_OK);
}

// helper class for GuessMimeType() tests
class SniffingTestFile {
public:
        SniffingTestFile(string name, string extensionType, string contentType,
                                         const void *data = NULL, int32 size = -1, string metaType = "")
                : name(name)
                , extensionType(extensionType)
                , contentType(contentType)
                , data(NULL)
                , size(0)
                , metaType(metaType)
        {
                // replace wildcard types
                if (this->extensionType == "")
                        this->extensionType = "application/octet-stream";
                if (this->contentType == "")
                        this->contentType = "application/octet-stream";
                // copy data
                if (data) {
                        if (size == -1)
                                this->size = strlen((const char*)data) + 1;
                        else
                                this->size = size;
                        this->data = new char[this->size];
                        memcpy(this->data, data, this->size);
                }
        }

        ~SniffingTestFile()
        {
                delete[] data;
        }

        status_t Create()
        {
                BFile file(name.c_str(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
                ssize_t error = file.InitCheck();
                if (error == B_OK && data) {
                        ssize_t written = file.Write(data, size);
                        if (written < 0)
                                error = written;
                        else if (written != size)
                                error = B_ERROR;
                }
                if (!error && metaType.length() > 0) {
                        error = file.WriteAttr("META:TYPE", B_STRING_TYPE, 0, metaType.c_str(),
                                metaType.length()+1);
                        error = error == (ssize_t)(metaType.length()+1) ? B_OK : error;
                }
                return error;
        }

        status_t Delete()
        {
                return BEntry(name.c_str()).Remove();
        }

        string  name;
        string  extensionType;
        string  contentType;
        char    *data;
        int32   size;
        string metaType;
};

// SniffingTest
void
MimeTypeTest::SniffingTest()
{
        // tests:
        // * GuessMimeType()

        // install some test types with sniffer rules
        {
                BMimeType type;
                CHK(type.SetTo(testType) == B_OK);
                CHK(type.Install() == B_OK);
                CHK(type.SetSnifferRule("0.5 [0:1] ('ABCD_EFGH' & 0xffffffff00ffffffff)")
                        == B_OK);
                CHK(type.SetTo(testType1) == B_OK);
                CHK(type.Install() == B_OK);
                CHK(type.SetSnifferRule("0.4 ('ABCD')") == B_OK);
                CHK(type.SetTo(testType2) == B_OK);
                CHK(type.Install() == B_OK);
#if TEST_R5
                // This rule is invalid!
                CHK(type.SetSnifferRule("0.4 [0] ('XYZ') | [0:5] ('CD  E')") == B_OK);
#else
//              CHK(type.SetSnifferRule("0.4 ([0] 'XYZ' | [0:5] 'CD  E')") == B_OK);
#endif
                CHK(type.SetTo(testType3) == B_OK);
                CHK(type.Install() == B_OK);
                CHK(type.SetSnifferRule("0.3 [0:8] ('ABCD' | 'EFGH')") == B_OK);
                CHK(type.SetTo(testType4) == B_OK);
                CHK(type.Install() == B_OK);
                CHK(type.SetSnifferRule("0.2 [0:3] ('ABCD' | 'abcd')") == B_OK);
                CHK(type.SetTo(testType5) == B_OK);
                CHK(type.Install() == B_OK);
                CHK(type.SetSnifferRule("0.2 ('LMNO' & 0xfffeffff)") == B_OK);
        }

        SniffingTestFile files[] = {
                SniffingTestFile(string(testDir) + "/file1.cpp",
                                                 "text/x-source-code", ""),
                SniffingTestFile(string(testDir) + "/file2.gif",
                                                 "image/gif", ""),
                SniffingTestFile(string(testDir) + "/file3",
                                                 "", "text/html",
                                                 "<html>\n<body>\n</body></html>\n"),
                SniffingTestFile(string(testDir) + "/file4.cpp",
                                                 "text/x-source-code", "text/html",
                                                 "<html>\n<body>\n</body></html>\n"),
                SniffingTestFile(string(testDir) + "/file5", "", testType1, "ABCD"),
                SniffingTestFile(string(testDir) + "/file6", "", testType3, " ABCD"),
                SniffingTestFile(string(testDir) + "/file7", "", testType4, "abcd"),
                SniffingTestFile(string(testDir) + "/file8", "", testType3,
                                                 " ABCDEFGH"),
                SniffingTestFile(string(testDir) + "/file9", "", testType,
                                                 " ABCD EFGH"),
//              SniffingTestFile(string(testDir) + "/file10", "", testType2,
                SniffingTestFile(string(testDir) + "/file10", "", testType3,
                                                 " ABCD  EFGH"),
                SniffingTestFile(string(testDir) + "/file11", "", testType5,
                                                 "LMNO"),
                SniffingTestFile(string(testDir) + "/file12", "", testType5,
                                                 "LLNO"),
                SniffingTestFile(string(testDir) + "/file13", "", "",
                                                 "LNNO"),
                // meta mime test
// bonefish: TODO: Now that content sniffing is enabled again, this doesn't
// work properly anymore, since there are actually three types involved: The
// extension type ("text/html"), the content type (also "text/html") and the
// real type ("application/x-vnd.be-meta-mime") which is concluded from the
// existence of the "META:TYPE" attribute rather than the extension or content.
//#if !TEST_R5
//              SniffingTestFile(string(testDir) + "/file14.html",
//                                               "text/html", "application/x-vnd.be-meta-mime",
//                                               "<html>\n<body>\n</body></html>\n", -1,
//                                               "fake-meta-mime-string"),
//#endif        // !TEST_R5
        };
        int fileCount = sizeof(files) / sizeof(SniffingTestFile);
        for (int32 i = 0; i < fileCount; i++) {
                NextSubTest();
                SniffingTestFile &file = files[i];
                const char *filename = file.name.c_str();
//printf("file: %s\n", filename);
                const char *extensionType = file.extensionType.c_str();
                const char *contentType = file.contentType.c_str();
                const char *realType = contentType;
                if (file.contentType == "application/octet-stream")
                        realType = extensionType;
                // GuessMimeType(const char*,)
                BMimeType type;
                CHK(BMimeType::GuessMimeType(filename, &type) == B_OK);
//printf("type: `%s', extensionType: `%s'\n", type.Type(), extensionType);
                CHK(type == extensionType);
                type.Unset();
                // GuessMimeType(const void*, int32,)
                if (file.data != NULL) {
                        CHK(BMimeType::GuessMimeType(file.data, file.size, &type) == B_OK);
if (!(type == contentType))
printf("type: %s, should be: %s\n", type.Type(), realType);
                        CHK(type == contentType);
                        type.Unset();
                }
                CHK(file.Create() == B_OK);
                // set BEOS:TYPE to something confusing ;-)
                BNode node;
                CHK(node.SetTo(filename) == B_OK);
                CHK(WriteStringAttr(node, "BEOS:TYPE", "application/x-person") == B_OK);
                // GuessMimeType(const ref*,)
                entry_ref ref;
                CHK(get_ref_for_path(filename, &ref) == B_OK);
                CHK(BMimeType::GuessMimeType(&ref, &type) == B_OK);
if (!(type == realType))
printf("type: %s, should be: %s (file == '%s')\n", type.Type(), realType, filename);
                CHK(type == realType);
                type.Unset();
                CHK(file.Delete() == B_OK);
        }

        // GuessMimeType(const ref*,), invalid/abstract entry
        {
                NextSubTest();
                string filename = string(testDir) + "/file100.cpp";
                BMimeType type;
                entry_ref ref;
// invalid entry_ref: R5: Is fine! Haiku: no dice
#if TEST_R5
                CHK(BMimeType::GuessMimeType(&ref, &type) == B_OK);
                CHK(type == "application/octet-stream");
#else
                CHK(BMimeType::GuessMimeType(&ref, &type) != B_OK);
#endif
                // abstract entry_ref
                CHK(get_ref_for_path(filename.c_str(), &ref) == B_OK);
                // R5: B_NAME_NOT_FOUND, Haiku:
                CHK(BMimeType::GuessMimeType(&ref, &type) != B_OK);
        }

        // bad args
        {
                NextSubTest();
                SniffingTestFile &file = files[0];
                CHK(file.Create() == B_OK);
                const char *filename = file.name.c_str();
                entry_ref ref;
                CHK(get_ref_for_path(filename, &ref) == B_OK);
                BMimeType type;
                // NULL BMimeType
                CHK(BMimeType::GuessMimeType(filename, NULL) == B_BAD_VALUE);
                CHK(BMimeType::GuessMimeType(file.data, file.size, NULL)
                        == B_BAD_VALUE);
                CHK(BMimeType::GuessMimeType(&ref, NULL) == B_BAD_VALUE);
                // NULL filename/ref/data
                CHK(BMimeType::GuessMimeType((const char*)NULL, &type) == B_BAD_VALUE);
                CHK(BMimeType::GuessMimeType(NULL, 10, &type) == B_BAD_VALUE);
                CHK(BMimeType::GuessMimeType((const entry_ref*)NULL, &type)
                        == B_BAD_VALUE);
                // NULL BMimeType and filename/ref/data
                CHK(BMimeType::GuessMimeType((const char*)NULL, NULL) == B_BAD_VALUE);
                CHK(BMimeType::GuessMimeType(NULL, 10, NULL) == B_BAD_VALUE);
                CHK(BMimeType::GuessMimeType((const entry_ref*)NULL, NULL)
                        == B_BAD_VALUE);
                CHK(file.Delete() == B_OK);
        }
}


/* KEY:
   + == Tests implemented
   * == Function implemented
*/

/* Ingo's functions:

        // initialization
+*      BMimeType();
+*      BMimeType(const char *mimeType);
(*      virtual ~BMimeType();)

+*      status_t SetTo(const char *mimeType);
+*      status_t SetType(const char *mimeType);
+*      void Unset();
+*      status_t InitCheck() const;

        // string access
+*      const char *Type() const;
+*      bool IsValid() const;
+*      static bool IsValid(const char *mimeType);
+*      bool IsSupertypeOnly() const;
+*      status_t GetSupertype(BMimeType *superType) const;
+*      bool Contains(const BMimeType *type) const;
+*      bool operator==(const BMimeType &type) const;
+*      bool operator==(const char *type) const;

        // MIME database monitoring
+       static status_t StartWatching(BMessenger target);
+       static status_t StopWatching(BMessenger target);

        // C functions
+       int update_mime_info(const char *path, int recursive, int synchronous,
                                                 int force);
+       status_t create_app_meta_mime(const char *path, int recursive,
                                                                  int synchronous, int force);
+       status_t get_device_icon(const char *dev, void *icon, int32 size);

        // sniffer rule manipulation
+       status_t GetSnifferRule(BString *result) const;
+       status_t SetSnifferRule(const char *);
+       static status_t CheckSnifferRule(const char *rule, BString *parseError);

        // sniffing
+       status_t GuessMimeType(const entry_ref *file, BMimeType *result);
+       static status_t GuessMimeType(const void *buffer, int32 length,
                                                                  BMimeType *result);
+       static status_t GuessMimeType(const char *filename, BMimeType *result);
*/


/* Tyler's functions:

        // MIME database access
+       status_t Install();
+       status_t Delete();
+       bool IsInstalled() const;
+       status_t GetIcon(BBitmap *icon, icon_size size) const;
+       status_t GetPreferredApp(char *signature, app_verb verb = B_OPEN) const;
+       status_t GetAttrInfo(BMessage *info) const;
+       status_t GetFileExtensions(BMessage *extensions) const;
+       status_t GetShortDescription(char *description) const;
+       status_t GetLongDescription(char *description) const;
        status_t GetSupportingApps(BMessage *signatures) const;

+       status_t SetIcon(const BBitmap *icon, icon_size size);
+       status_t SetPreferredApp(const char *signature, app_verb verb = B_OPEN);
+       status_t SetAttrInfo(const BMessage *info);
+       status_t SetFileExtensions(const BMessage *extensions);
+       status_t SetShortDescription(const char *description);
+       status_t SetLongDescription(const char *description);

+       static status_t GetInstalledSupertypes(BMessage *super_types);
+       static status_t GetInstalledTypes(BMessage *types);
+       static status_t GetInstalledTypes(const char *super_type,
                                                                          BMessage *subtypes);
+       static status_t GetWildcardApps(BMessage *wild_ones);

+       status_t GetAppHint(entry_ref *ref) const;
+       status_t SetAppHint(const entry_ref *ref);

+       status_t GetIconForType(const char *type, BBitmap *icon,
                                                        icon_size which) const;
+       status_t SetIconForType(const char *type, const BBitmap *icon,
                                                        icon_size which);
*/