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

#include <stdio.h>
#include <string>
#include <unistd.h>

#include <Application.h>
#include <Bitmap.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <fs_attr.h>
#include <fs_info.h>
#include <Node.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <Resources.h>
#include <Roster.h>
#include <String.h>
#include <TypeConstants.h>
#include <Volume.h>
#include <VolumeRoster.h>

#include <cppunit/Test.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestSuite.h>
#include <TestApp.h>
#include <TestShell.h>
#include <TestUtils.h>
#include <cppunit/TestAssert.h>

#include "VolumeTest.h"
#include "../app/bmessenger/Helpers.h"

// test dirs/files/types
static const char *testDir                      = "/tmp/testDir";
static const char *testFile1            = "/tmp/testDir/file1";
static const char *testMountPoint       = "/tmp/testDir/mount_point";

// icon_equal
static
bool
icon_equal(const BBitmap *icon1, const BBitmap *icon2)
{
        return (icon1->Bounds() == icon2->Bounds()
                        && icon1->BitsLength() == icon2->BitsLength()
                        && memcmp(icon1->Bits(), icon2->Bits(), icon1->BitsLength()) == 0);
}

// Suite
CppUnit::Test*
VolumeTest::Suite() {
        CppUnit::TestSuite *suite = new CppUnit::TestSuite();
        typedef CppUnit::TestCaller<VolumeTest> TC;
                
        suite->addTest( new TC("BVolume::Init Test1",
                                                   &VolumeTest::InitTest1) );
        suite->addTest( new TC("BVolume::Init Test2",
                                                   &VolumeTest::InitTest2) );
        suite->addTest( new TC("BVolume::Assignment Test",
                                                   &VolumeTest::AssignmentTest) );
        suite->addTest( new TC("BVolume::Comparisson Test",
                                                   &VolumeTest::ComparissonTest) );
        suite->addTest( new TC("BVolume::SetName Test",
                                                   &VolumeTest::SetNameTest) );
        suite->addTest( new TC("BVolume::BadValues Test",
                                                   &VolumeTest::BadValuesTest) );
        suite->addTest( new TC("BVolumeRoster::Iteration Test",
                                                   &VolumeTest::IterationTest) );
        suite->addTest( new TC("BVolumeRoster::Watching Test",
                                                   &VolumeTest::WatchingTest) );

        return suite;
}               

// setUp
void
VolumeTest::setUp()
{
        BasicTest::setUp();
        // create test dir and files
        execCommand(
                string("mkdir ") + testDir
        );
        // create and mount image
        createVolume(testFile1, testMountPoint, 1);
        // create app
        fApplication = new BTestApp("application/x-vnd.obos.volume-test");
        if (fApplication->Init() != B_OK) {
                fprintf(stderr, "Failed to initialize application.\n");
                delete fApplication;
                fApplication = NULL;
        }
}
        
// tearDown
void
VolumeTest::tearDown()
{
        // delete the application
        if (fApplication) {
                fApplication->Terminate();
                delete fApplication;
                fApplication = NULL;
        }
        // unmount and delete image
        deleteVolume(testFile1, testMountPoint);
        // delete the test dir
        execCommand(string("rm -rf ") + testDir);

        BasicTest::tearDown();
}

// CheckVolume
static
void
CheckVolume(BVolume &volume, dev_t device, status_t error)
{
        CHK(volume.InitCheck() == error);
        if (error == B_OK) {
                fs_info info;
                CHK(fs_stat_dev(device, &info) == 0);
                // device
                CHK(volume.Device() == device);
                // root dir
                BDirectory rootDir;
                CHK(volume.GetRootDirectory(&rootDir) == B_OK);
                node_ref rootNode;
                rootNode.device = device;
                rootNode.node = info.root;
                BDirectory actualRootDir(&rootNode);
                CHK(rootDir == actualRootDir);
                // capacity, free bytes
                CHK(volume.Capacity() == info.total_blocks * info.block_size);
                CHK(volume.FreeBytes() == info.free_blocks * info.block_size);
                // name
                char name[B_FILE_NAME_LENGTH];
                CHK(volume.GetName(name) == B_OK);
                CHK(BString(name) == info.volume_name);
                // icons
                // mini
                BBitmap miniIcon(BRect(0, 0, 15, 15), B_CMAP8);
                BBitmap miniIcon2(BRect(0, 0, 15, 15), B_CMAP8);
                status_t iconError = get_device_icon(info.device_name,
                                                                                         miniIcon2.Bits(), B_MINI_ICON);
                CHK(volume.GetIcon(&miniIcon, B_MINI_ICON) == iconError);
                if (iconError == B_OK)
                        CHK(icon_equal(&miniIcon, &miniIcon2));
                // large
                BBitmap largeIcon(BRect(0, 0, 31, 31), B_CMAP8);
                BBitmap largeIcon2(BRect(0, 0, 31, 31), B_CMAP8);
                iconError = get_device_icon(info.device_name, largeIcon2.Bits(),
                                                                        B_LARGE_ICON);
                CHK(volume.GetIcon(&largeIcon, B_LARGE_ICON) == iconError);
                if (iconError == B_OK)
                        CHK(icon_equal(&largeIcon, &largeIcon2));
                // flags
                CHK(volume.IsRemovable() == bool(info.flags & B_FS_IS_REMOVABLE));
                CHK(volume.IsReadOnly() == bool(info.flags & B_FS_IS_READONLY));
                CHK(volume.IsPersistent() == bool(info.flags & B_FS_IS_PERSISTENT));
                CHK(volume.IsShared() == bool(info.flags & B_FS_IS_SHARED));
                CHK(volume.KnowsMime() == bool(info.flags & B_FS_HAS_MIME));
                CHK(volume.KnowsAttr() == bool(info.flags & B_FS_HAS_ATTR));
                CHK(volume.KnowsQuery() == bool(info.flags & B_FS_HAS_QUERY));
        } else {
                CHK(volume.Device() == -1);
                // root dir
                BDirectory rootDir;
                CHK(volume.GetRootDirectory(&rootDir) == B_BAD_VALUE);
                // capacity, free bytes
                CHK(volume.Capacity() == B_BAD_VALUE);
                CHK(volume.FreeBytes() == B_BAD_VALUE);
                // name
                char name[B_FILE_NAME_LENGTH];
                CHK(volume.GetName(name) == B_BAD_VALUE);
                // icons
                // mini
                BBitmap miniIcon(BRect(0, 0, 15, 15), B_CMAP8);
                CHK(volume.GetIcon(&miniIcon, B_MINI_ICON) == B_BAD_VALUE);
                // large
                BBitmap largeIcon(BRect(0, 0, 31, 31), B_CMAP8);
                CHK(volume.GetIcon(&largeIcon, B_LARGE_ICON) == B_BAD_VALUE);
                // flags
                CHK(volume.IsRemovable() == false);
                CHK(volume.IsReadOnly() == false);
                CHK(volume.IsPersistent() == false);
                CHK(volume.IsShared() == false);
                CHK(volume.KnowsMime() == false);
                CHK(volume.KnowsAttr() == false);
                CHK(volume.KnowsQuery() == false);
        }
}

// AssertNotBootVolume
static
void
AssertNotBootVolume(const BVolume &volume)
{
        CHK(volume.InitCheck() == B_OK);
        dev_t bootDevice = dev_for_path("/boot");
        CHK(bootDevice >= 0);
        CHK(volume.Device() != bootDevice);
}

// InitTest1
void
VolumeTest::InitTest1()
{
        // 1. BVolume(void)
        {
                BVolume volume;
                CheckVolume(volume, -1, B_NO_INIT);
        }
        // 2. BVolume(dev_t dev)
        // volumes for testing
        const char *volumes[] = {
                "/boot",
                "/",
                "/dev",
                "/pipe",
                "/unknown",
                testMountPoint
        };
        int32 volumeCount = sizeof(volumes) / sizeof(const char*);
        for (int32 i = 0; i < volumeCount; i++) {
                NextSubTest();
                const char *volumeRootDir = volumes[i];
                dev_t device = dev_for_path(volumeRootDir);
                BVolume volume(device);
                CheckVolume(volume, device, (device >= 0 ? B_OK : B_BAD_VALUE));
        }
        // invalid device ID
        NextSubTest();
        {
                BVolume volume(-2);
                CHK(volume.InitCheck() == B_BAD_VALUE);
        }
        // invalid device ID
        NextSubTest();
        {
                dev_t device = 213;
                fs_info info;
                while (fs_stat_dev(device, &info) == 0)
                        device++;
                BVolume volume(device);
                CHK(volume.InitCheck() == B_ENTRY_NOT_FOUND);
        }
}

// InitTest2
void
VolumeTest::InitTest2()
{
        // volumes for testing
        const char *volumes[] = {
                "/boot",
                "/",
                "/dev",
                "/pipe",
                "/unknown",
                testMountPoint
        };
        int32 volumeCount = sizeof(volumes) / sizeof(const char*);
        BVolume volume1;
        for (int32 i = 0; i < volumeCount; i++) {
                NextSubTest();
                const char *volumeRootDir = volumes[i];
                dev_t device = dev_for_path(volumeRootDir);
                status_t initError = (device >= 0 ? B_OK : B_BAD_VALUE);
                // reinit already initialized volume
                CHK(volume1.SetTo(device) == initError);
                CheckVolume(volume1, device, initError);
                // init fresh volume
                BVolume volume2;
                CHK(volume2.SetTo(device) == initError);
                CheckVolume(volume2, device, initError);
                // uninit volume
                volume2.Unset();
                CheckVolume(volume2, device, B_NO_INIT);
        }
        // invalid device ID
        NextSubTest();
        {
                BVolume volume;
                CHK(volume.SetTo(-2) == B_BAD_VALUE);
                CHK(volume.InitCheck() == B_BAD_VALUE);
        }
        // invalid device ID
        NextSubTest();
        {
                dev_t device = 213;
                fs_info info;
                while (fs_stat_dev(device, &info) == 0)
                        device++;
                BVolume volume;
                CHK(volume.SetTo(device) == B_ENTRY_NOT_FOUND);
                CHK(volume.InitCheck() == B_ENTRY_NOT_FOUND);
        }
}

// AssignmentTest
void
VolumeTest::AssignmentTest()
{
        // volumes for testing
        const char *volumes[] = {
                "/boot",
                "/",
                "/dev",
                "/pipe",
                "/unknown",
                testMountPoint
        };
        int32 volumeCount = sizeof(volumes) / sizeof(const char*);
        BVolume volume1;
        for (int32 i = 0; i < volumeCount; i++) {
                NextSubTest();
                const char *volumeRootDir = volumes[i];
                dev_t device = dev_for_path(volumeRootDir);
                status_t initError = (device >= 0 ? B_OK : B_BAD_VALUE);
                BVolume volume3(device);
                CheckVolume(volume3, device, initError);
                // assignment operation
                CHK(&(volume1 = volume3) == &volume1);
                CheckVolume(volume1, device, initError);
                // copy constructor
                BVolume volume2(volume3);
                CheckVolume(volume2, device, initError);
        }
}

// ComparissonTest
void
VolumeTest::ComparissonTest()
{
        // volumes for testing
        const char *volumes[] = {
                "/boot",
                "/",
                "/dev",
                "/pipe",
                "/unknown",
                testMountPoint
        };
        int32 volumeCount = sizeof(volumes) / sizeof(const char*);
        for (int32 i = 0; i < volumeCount; i++) {
                NextSubTest();
                const char *volumeRootDir = volumes[i];
                dev_t device = dev_for_path(volumeRootDir);
                status_t initError = (device >= 0 ? B_OK : B_BAD_VALUE);
                BVolume volume(device);
                CheckVolume(volume, device, initError);
                for (int32 k = 0; k < volumeCount; k++) {
                        const char *volumeRootDir2 = volumes[k];
                        dev_t device2 = dev_for_path(volumeRootDir2);
                        status_t initError2 = (device2 >= 0 ? B_OK : B_BAD_VALUE);
                        BVolume volume2(device2);
                        CheckVolume(volume2, device2, initError2);
                        bool equal = (i == k
                                                  || initError == initError2 && initError2 != B_OK);
                        CHK((volume == volume2) == equal);
                        CHK((volume != volume2) == !equal);
                }
        }
}

// SetNameTest
void
VolumeTest::SetNameTest()
{
        // status_t SetName(const char* name);
        dev_t device = dev_for_path(testMountPoint);
        BVolume volume(device);
        CheckVolume(volume, device, B_OK);
        AssertNotBootVolume(volume);
        // set a new name
        NextSubTest();
        const char *newName = "a new name";
        CHK(volume.SetName(newName) == B_OK);
        char name[B_FILE_NAME_LENGTH];
        CHK(volume.GetName(name) == B_OK);
        CHK(string(newName) == name);
        CheckVolume(volume, device, B_OK);
        // set another name
        NextSubTest();
        const char *newName2 = "another name";
        CHK(volume.SetName(newName2) == B_OK);
        CHK(volume.GetName(name) == B_OK);
        CHK(string(newName2) == name);
        CheckVolume(volume, device, B_OK);
        // GetName() with NULL buffer
// R5: crashes when passing a NULL pointer
#ifndef TEST_R5
        NextSubTest();
        CHK(volume.GetName(NULL) == B_BAD_VALUE);
#endif
        // bad name
// R5: crashes when passing a NULL pointer
#ifndef TEST_R5
        NextSubTest();
        CHK(volume.SetName(NULL) == B_BAD_VALUE);
#endif
        // uninitialized volume
        NextSubTest();
        volume.Unset();
        CHK(volume.SetName(newName) == B_BAD_VALUE);
}

// BadValuesTest
void
VolumeTest::BadValuesTest()
{
        BVolume volume(dev_for_path("/boot"));
        CHK(volume.InitCheck() == B_OK);
        // NULL arguments
// R5: crashes, when passing a NULL BDirectory.
#ifndef TEST_R5
        NextSubTest();
        CHK(volume.GetRootDirectory(NULL) == B_BAD_VALUE);
#endif
        NextSubTest();
        CHK(volume.GetIcon(NULL, B_MINI_ICON) == B_BAD_VALUE);
        CHK(volume.GetIcon(NULL, B_LARGE_ICON) == B_BAD_VALUE);
        // incompatible icon formats
// R5: returns B_OK
#ifndef TEST_R5
        NextSubTest();
        // mini
        BBitmap largeIcon(BRect(0, 0, 31, 31), B_CMAP8);
        CHK(volume.GetIcon(&largeIcon, B_MINI_ICON) == B_BAD_VALUE);
        // large
        BBitmap miniIcon(BRect(0, 0, 15, 15), B_CMAP8);
        CHK(volume.GetIcon(&miniIcon, B_LARGE_ICON) == B_BAD_VALUE);
#endif
}


// BVolumeRoster tests

//  GetAllDevices
static
void
GetAllDevices(set<dev_t> &devices)
{
//printf("GetAllDevices()\n");
        int32 cookie = 0;
        dev_t device;
        while ((device = next_dev(&cookie)) >= 0)
{
//printf("  device: %ld\n", device);
//BVolume dVolume(device);
//char name[B_FILE_NAME_LENGTH];
//dVolume.GetName(name);
//BDirectory rootDir;
//dVolume.GetRootDirectory(&rootDir);
//BEntry rootEntry;
//rootDir.GetEntry(&rootEntry);
//BPath rootPath;
//rootEntry.GetPath(&rootPath);
//printf("  name: `%s', root: `%s'\n", name, rootPath.Path());
                devices.insert(device);
}
//printf("GetAllDevices() done\n");
}

// IterationTest
void
VolumeTest::IterationTest()
{
        // status_t GetBootVolume(BVolume *volume)
        NextSubTest();
        BVolumeRoster roster;
        BVolume volume;
        CHK(roster.GetBootVolume(&volume) == B_OK);
        dev_t device = dev_for_path("/boot");
        CHK(device >= 0);
        CheckVolume(volume, device, B_OK);

        // status_t GetNextVolume(BVolume *volume)
        // void Rewind()
        set<dev_t> allDevices;
        GetAllDevices(allDevices);
        int32 allDevicesCount = allDevices.size();
        for (int32 i = 0; i <= allDevicesCount; i++) {
                NextSubTest();
                // iterate through the first i devices
                set<dev_t> devices(allDevices);
                volume.Unset();
                int32 checkCount = i;
                while (--checkCount >= 0 && roster.GetNextVolume(&volume) == B_OK) {
                        device = volume.Device();
                        CHK(device >= 0);
                        CheckVolume(volume, device, B_OK);
                        CHK(devices.find(device) != devices.end());
                        devices.erase(device);
                }
                // rewind and iterate through all devices
                devices = allDevices;
                roster.Rewind();
                volume.Unset();
                status_t error;
                while ((error = roster.GetNextVolume(&volume)) == B_OK) {
                        device = volume.Device();
                        CHK(device >= 0);
                        CheckVolume(volume, device, B_OK);
                        CHK(devices.find(device) != devices.end());
                        devices.erase(device);
                }
                CHK(error == B_BAD_VALUE);
                CHK(devices.empty());
                roster.Rewind();
        }

        // bad argument
// R5: crashes when passing a NULL BVolume
#ifndef TEST_R5
        NextSubTest();
        CHK(roster.GetNextVolume(NULL) == B_BAD_VALUE);
#endif
}

// CheckWatchingMessage
static
void
CheckWatchingMessage(bool mounted, dev_t expectedDevice, BTestHandler &handler,
                                         node_ref nodeRef = node_ref())
{
        snooze(100000);
        // get the message
        BMessageQueue &queue = handler.Queue();
        BMessage *_message = queue.NextMessage();
        CHK(_message);
        BMessage message(*_message);
        delete _message;
        // check the message
        if (mounted) {
                // volume mounted
                int32 opcode;
                dev_t device;
                dev_t parentDevice;
                ino_t directory;
                CHK(message.FindInt32("opcode", &opcode) == B_OK);
                CHK(message.FindInt32("new device", &device) == B_OK);
                CHK(message.FindInt32("device", &parentDevice) == B_OK);
                CHK(message.FindInt64("directory", &directory) == B_OK);
                CHK(opcode == B_DEVICE_MOUNTED);
                CHK(device == expectedDevice);
                CHK(parentDevice == nodeRef.device);
                CHK(directory == nodeRef.node);
        } else {
                // volume unmounted
                int32 opcode;
                dev_t device;
                CHK(message.FindInt32("opcode", &opcode) == B_OK);
                CHK(message.FindInt32("device", &device) == B_OK);
                CHK(opcode == B_DEVICE_UNMOUNTED);
                CHK(device == expectedDevice);
        }
}

// WatchingTest
void
VolumeTest::WatchingTest()
{
        // status_t StartWatching(BMessenger msngr=be_app_messenger);
        // void StopWatching(void);
        // BMessenger Messenger(void) const;

        // start watching
        NextSubTest();
        BVolumeRoster roster;
        CHK(!roster.Messenger().IsValid());
        BMessenger target(&fApplication->Handler());
        CHK(roster.StartWatching(target) == B_OK);
        CHK(roster.Messenger() == target);
        dev_t device = dev_for_path(testMountPoint);
        CHK(device >= 0);
        // unmount volume
        NextSubTest();
        deleteVolume(testFile1, testMountPoint, false);
        CHK(roster.Messenger() == target);
        CheckWatchingMessage(false, device, fApplication->Handler());
        // get the node_ref of the mount point
        node_ref nodeRef;
        CHK(BDirectory(testMountPoint).GetNodeRef(&nodeRef) == B_OK);
        // mount volume
        NextSubTest();
        createVolume(testFile1, testMountPoint, 1, false);
        CHK(roster.Messenger() == target);
        device = dev_for_path(testMountPoint);
        CHK(device >= 0);
        CheckWatchingMessage(true, device, fApplication->Handler(), nodeRef);
        // start watching with another target
        BTestHandler *handler2 = fApplication->CreateTestHandler();
        BMessenger target2(handler2);
        CHK(roster.StartWatching(target2) == B_OK);
        CHK(roster.Messenger() == target2);
        // unmount volume
        NextSubTest();
        deleteVolume(testFile1, testMountPoint, false);
        CHK(roster.Messenger() == target2);
        CheckWatchingMessage(false, device, *handler2);
        // mount volume
        NextSubTest();
        createVolume(testFile1, testMountPoint, 1, false);
        CHK(roster.Messenger() == target2);
        device = dev_for_path(testMountPoint);
        CHK(device >= 0);
        CheckWatchingMessage(true, device, *handler2, nodeRef);
        // stop watching
        NextSubTest();
        roster.StopWatching();
        CHK(!roster.Messenger().IsValid());
        // unmount, mount volume
        NextSubTest();
        deleteVolume(testFile1, testMountPoint, false);
        createVolume(testFile1, testMountPoint, 1, false);
        snooze(100000);
        CHK(fApplication->Handler().Queue().IsEmpty());
        CHK(handler2->Queue().IsEmpty());

        // try start watching with a bad messenger
        NextSubTest();
        CHK(roster.StartWatching(BMessenger()) == B_ERROR);
}