diff --git a/src/tests/kits/storage/testapps/Jamfile b/src/tests/kits/storage/testapps/Jamfile index 2698a85667..0b6ef3f548 100644 --- a/src/tests/kits/storage/testapps/Jamfile +++ b/src/tests/kits/storage/testapps/Jamfile @@ -20,3 +20,5 @@ SimpleTest PathMonitorTest : PathMonitorTest.cpp PathMonitor.cpp : be $(TARGET_LIBSTDC++) ; + +SimpleTest PathMonitorTest2 : PathMonitorTest2.cpp : be $(TARGET_LIBSTDC++) ; diff --git a/src/tests/kits/storage/testapps/PathMonitorTest2.cpp b/src/tests/kits/storage/testapps/PathMonitorTest2.cpp new file mode 100644 index 0000000000..0aa8400bf4 --- /dev/null +++ b/src/tests/kits/storage/testapps/PathMonitorTest2.cpp @@ -0,0 +1,1438 @@ +/* + * Copyright 2013, Haiku, Inc. + * Distributed under the terms of the MIT License. + * + * Authors: + * Ingo Weinhold, ingo_weinhold@gmx.de + */ + + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +using BPrivate::BPathMonitor; + + +static const char* const kTestBasePath = "/tmp/path-monitor-test"; +static const bigtime_t kMaxNotificationDelay = 100000; + + +#define FATAL(...) \ + do { \ + throw FatalException( \ + BString().SetToFormat("%s:%d: ", __FILE__, __LINE__) \ + << BString().SetToFormat(__VA_ARGS__)); \ + } while (false) + +#define FATAL_IF_ERROR(error, ...) \ + do { \ + status_t _fatalError = (error); \ + if (_fatalError < 0) { \ + throw FatalException( \ + BString().SetToFormat("%s:%d: ", __FILE__, __LINE__) \ + << BString().SetToFormat(__VA_ARGS__) \ + << BString().SetToFormat( \ + ": %s\n", strerror(_fatalError))); \ + } \ + } while (false) + +#define FATAL_IF_POSIX_ERROR(error, ...) \ + if ((error) < 0) \ + FATAL_IF_ERROR(errno, __VA_ARGS__) + +#define FAIL(...) \ + throw TestException(BString().SetToFormat(__VA_ARGS__)) + + +struct TestException { + TestException(const BString& message) + : + fMessage(message) + { + } + + const BString& Message() const + { + return fMessage; + } + +private: + BString fMessage; +}; + + +struct FatalException { + FatalException(const BString& message) + : + fMessage(message) + { + } + + const BString& Message() const + { + return fMessage; + } + +private: + BString fMessage; +}; + + +static BString +test_path(const BString& maybeRelativePath) +{ + if (maybeRelativePath.ByteAt(0) == '/') + return maybeRelativePath; + + BString path; + path.SetToFormat("%s/%s", kTestBasePath, maybeRelativePath.String()); + if (path.IsEmpty()) + FATAL_IF_ERROR(B_NO_MEMORY, "Failed to make absolute path"); + return path; +} + + +static BString +node_ref_to_string(const node_ref& nodeRef) +{ + return BString().SetToFormat("%" B_PRIdDEV ":%" B_PRIdINO, nodeRef.device, + nodeRef.node); +} + + +static BString +entry_ref_to_string(const entry_ref& entryRef) +{ + return BString().SetToFormat("%" B_PRIdDEV ":%" B_PRIdINO ":\"%s\"", + entryRef.device, entryRef.directory, entryRef.name); +} + + +static BString +indented_string(const char* string, const char* indent, + const char* firstIndent = NULL) +{ + const char* end = string + strlen(string); + BString result; + const char* line = string; + while (line < end) { + const char* lineEnd = strchr(line, '\n'); + lineEnd = lineEnd != NULL ? lineEnd + 1 : end; + result + << (line == string && firstIndent != NULL ? firstIndent : indent); + result.Append(line, lineEnd - line); + line = lineEnd; + } + + return result; +} + + +static BString +message_to_string(const BMessage& message) +{ + BString result; + + char* name; + type_code typeCode; + int32 count; + for (int32 i = 0; + message.GetInfo(B_ANY_TYPE, i, &name, &typeCode, &count) == B_OK; + i++) { + if (i > 0) + result << '\n'; + + result << '"' << name << '"'; + BString type; + + switch (typeCode) { + case B_UINT8_TYPE: + case B_INT8_TYPE: + type << "int8"; + break; + + case B_UINT16_TYPE: + type = "u"; + case B_INT16_TYPE: + type << "int16"; + break; + + case B_UINT32_TYPE: + type = "u"; + case B_INT32_TYPE: + type << "int32"; + break; + + case B_UINT64_TYPE: + type = "u"; + case B_INT64_TYPE: + type << "int64"; + break; + + case B_STRING_TYPE: + type = "string"; + break; + + default: + { + int code = (int)typeCode; + type.SetToFormat("'%02x%02x%02x%02x'", code >> 24, + (code >> 16) & 0xff, (code >> 8) & 0xff, code & 0xff); + break; + } + } + + result << " (" << type << "):"; + + for (int32 k = 0; k < count; k++) { + BString value; + switch (typeCode) { + case B_UINT8_TYPE: + value << message.GetUInt8(name, k, 0); + break; + case B_INT8_TYPE: + value << message.GetInt8(name, k, 0); + break; + case B_UINT16_TYPE: + value << message.GetUInt16(name, k, 0); + break; + case B_INT16_TYPE: + value << message.GetInt16(name, k, 0); + break; + case B_UINT32_TYPE: + value << message.GetUInt32(name, k, 0); + break; + case B_INT32_TYPE: + value << message.GetInt32(name, k, 0); + break; + case B_UINT64_TYPE: + value << message.GetUInt64(name, k, 0); + break; + case B_INT64_TYPE: + value << message.GetInt64(name, k, 0); + break; + case B_STRING_TYPE: + value.SetToFormat("\"%s\"", message.GetString(name, k, "")); + break; + default: + { + const void* data; + ssize_t size; + if (message.FindData(name, typeCode, k, &data, &size) + != B_OK) { + value = "???"; + break; + } + + for (ssize_t l = 0; l < size; l++) { + uint8 v = ((const uint8*)data)[l]; + value << BString().SetToFormat("%02x", v); + } + break; + } + } + + if (k == 0 && count == 1) { + result << ' ' << value; + } else { + result << BString().SetToFormat("\n [%2" B_PRId32 "] ", k) + << value; + } + } + } + + return result; +} + + +static BString +watch_flags_to_string(uint32 flags) +{ + BString result; + if ((flags & B_WATCH_NAME) != 0) + result << "name "; + if ((flags & B_WATCH_STAT) != 0) + result << "stat "; + if ((flags & B_WATCH_ATTR) != 0) + result << "attr "; + if ((flags & B_WATCH_DIRECTORY) != 0) + result << "dir "; + if ((flags & B_WATCH_RECURSIVELY) != 0) + result << "recursive "; + if ((flags & B_WATCH_FILES_ONLY) != 0) + result << "files-only "; + if ((flags & B_WATCH_DIRECTORIES_ONLY) != 0) + result << "dirs-only "; + + if (!result.IsEmpty()) + result.Truncate(result.Length() - 1); + return result; +} + + +struct MonitoringInfo { + MonitoringInfo() + { + } + + MonitoringInfo(int32 opcode, const char* path) + : + fOpcode(opcode) + { + _Init(opcode, path); + } + + MonitoringInfo(int32 opcode, const char* fromPath, const char* toPath) + { + _Init(opcode, toPath); + + // init fFromEntryRef + BEntry entry; + FATAL_IF_ERROR(entry.SetTo(fromPath), + "Failed to init BEntry for \"%s\"", fromPath); + FATAL_IF_ERROR(entry.GetRef(&fFromEntryRef), + "Failed to get entry_ref for \"%s\"", fromPath); + } + + BString ToString() const + { + switch (fOpcode) { + case B_ENTRY_CREATED: + case B_ENTRY_REMOVED: + return BString().SetToFormat("%s %s at %s", + fOpcode == B_ENTRY_CREATED ? "created" : "removed", + node_ref_to_string(fNodeRef).String(), + entry_ref_to_string(fEntryRef).String()); + + case B_ENTRY_MOVED: + return BString().SetToFormat("moved %s from %s to %s", + node_ref_to_string(fNodeRef).String(), + entry_ref_to_string(fFromEntryRef).String(), + entry_ref_to_string(fEntryRef).String()); + + case B_STAT_CHANGED: + return BString().SetToFormat("stat changed for %s", + node_ref_to_string(fNodeRef).String()); + + case B_ATTR_CHANGED: + return BString().SetToFormat("attr changed for %s", + node_ref_to_string(fNodeRef).String()); + + case B_DEVICE_MOUNTED: + return BString().SetToFormat("volume mounted"); + + case B_DEVICE_UNMOUNTED: + return BString().SetToFormat("volume unmounted"); + } + + return BString(); + } + + bool Matches(const BMessage& message) const + { + if (fOpcode != message.GetInt32("opcode", -1)) + return false; + + switch (fOpcode) { + case B_ENTRY_CREATED: + case B_ENTRY_REMOVED: + { + NotOwningEntryRef entryRef; + node_ref nodeRef; + + if (message.FindInt32("device", &nodeRef.device) != B_OK + || message.FindInt64("node", &nodeRef.node) != B_OK + || message.FindInt64("directory", &entryRef.directory) + != B_OK + || message.FindString("name", (const char**)&entryRef.name) + != B_OK) { + return false; + } + entryRef.device = nodeRef.device; + + return nodeRef == fNodeRef && entryRef == fEntryRef; + } + + case B_ENTRY_MOVED: + { + NotOwningEntryRef fromEntryRef; + NotOwningEntryRef toEntryRef; + node_ref nodeRef; + + if (message.FindInt32("node device", &nodeRef.device) != B_OK + || message.FindInt64("node", &nodeRef.node) != B_OK + || message.FindInt32("device", &fromEntryRef.device) + != B_OK + || message.FindInt64("from directory", + &fromEntryRef.directory) != B_OK + || message.FindInt64("to directory", &toEntryRef.directory) + != B_OK + || message.FindString("from name", + (const char**)&fromEntryRef.name) != B_OK + || message.FindString("name", + (const char**)&toEntryRef.name) != B_OK) { + return false; + } + toEntryRef.device = fromEntryRef.device; + + return nodeRef == fNodeRef && toEntryRef == fEntryRef + && fromEntryRef == fFromEntryRef; + } + + case B_STAT_CHANGED: + case B_ATTR_CHANGED: + { + node_ref nodeRef; + + if (message.FindInt32("device", &nodeRef.device) != B_OK + || message.FindInt64("node", &nodeRef.node) != B_OK) { + return false; + } + + return nodeRef == fNodeRef; + } + + case B_DEVICE_MOUNTED: + case B_DEVICE_UNMOUNTED: + return true; + } + + return false; + } + +private: + void _Init(int32 opcode, const char* path) + { + fOpcode = opcode; + BEntry entry; + FATAL_IF_ERROR(entry.SetTo(path), "Failed to init BEntry for \"%s\"", + path); + FATAL_IF_ERROR(entry.GetRef(&fEntryRef), + "Failed to get entry_ref for \"%s\"", path); + FATAL_IF_ERROR(entry.GetNodeRef(&fNodeRef), + "Failed to get node_ref for \"%s\"", path); + } + +private: + int32 fOpcode; + node_ref fNodeRef; + entry_ref fEntryRef; + entry_ref fFromEntryRef; +}; + + +struct MonitoringInfoSet { + MonitoringInfoSet() + { + } + + MonitoringInfoSet& Add(const MonitoringInfo& info, bool expected = true) + { + if (expected) + fInfos.push_back(info); + return *this; + } + + MonitoringInfoSet& Add(int32 opcode, const BString& path, + bool expected = true) + { + return Add(MonitoringInfo(opcode, test_path(path)), expected); + } + + MonitoringInfoSet& Add(int32 opcode, const BString& fromPath, + const BString& toPath, bool expected = true) + { + return Add(MonitoringInfo(opcode, test_path(fromPath), + test_path(toPath)), expected); + } + + bool IsEmpty() const + { + return fInfos.empty(); + } + + int32 CountInfos() const + { + return fInfos.size(); + } + + const MonitoringInfo& InfoAt(int32 index) const + { + return fInfos[index]; + } + + void Remove(int32 index) + { + fInfos.erase(fInfos.begin() + index); + } + + BString ToString() const + { + BString result; + for (int32 i = 0; i < CountInfos(); i++) { + const MonitoringInfo& info = InfoAt(i); + if (i > 0) + result << '\n'; + result << info.ToString(); + } + return result; + } + +private: + std::vector fInfos; +}; + + +struct Test : private BLooper { + Test(const char* name) + : + fName(name), + fFlags(0), + fLooperThread(-1), + fNotifications(10, true), + fProcessedMonitoringInfos(), + fIsWatching(false) + { + } + + void Init(uint32 flags) + { + fFlags = flags; + + // delete and re-create the test directory + BEntry entry; + FATAL_IF_ERROR(entry.SetTo(kTestBasePath), + "Failed to init entry to \"%s\"", kTestBasePath); + + if (entry.Exists()) + _RemoveRecursively(entry); + + _CreateDirectory(kTestBasePath); + + fLooperThread = BLooper::Run(); + if (fLooperThread < 0) + FATAL_IF_ERROR(fLooperThread, "Failed to init looper"); + } + + void Delete() + { + if (fIsWatching) + BPathMonitor::StopWatching(this); + + if (fLooperThread < 0) { + delete this; + } else { + PostMessage(B_QUIT_REQUESTED); + wait_for_thread(fLooperThread, NULL); + } + } + + void Do() + { + bool recursive = (fFlags & B_WATCH_RECURSIVELY) != 0; + DoInternal(recursive && (fFlags & B_WATCH_DIRECTORIES_ONLY) != 0, + recursive && (fFlags & B_WATCH_FILES_ONLY) != 0, recursive, + !recursive && (fFlags & B_WATCH_DIRECTORY) == 0, + (fFlags & B_WATCH_STAT) != 0); + + // verify that there aren't any spurious notifications + snooze(kMaxNotificationDelay); + + AutoLocker locker(this); + if (fNotifications.IsEmpty()) + return; + + BString pendingNotifications + = "unexpected notification(s) at end of test:"; + for (int32 i = 0; BMessage* message = fNotifications.ItemAt(i); i++) { + pendingNotifications << '\n' + << indented_string(message_to_string(*message), " ", " * "); + } + + FAIL("%s%s", pendingNotifications.String(), + _ProcessedInfosString().String()); + } + + const BString& Name() const + { + return fName; + } + +protected: + ~Test() + { + } + + void StartWatching(const char* path) + { + BString absolutePath(test_path(path)); + FATAL_IF_ERROR(BPathMonitor::StartWatching(absolutePath, fFlags, this), + "Failed to start watching \"%s\"", absolutePath.String()); + fIsWatching = true; + } + + MonitoringInfo CreateDirectory(const char* path) + { + BString absolutePath(test_path(path)); + _CreateDirectory(absolutePath); + return MonitoringInfo(B_ENTRY_CREATED, absolutePath); + } + + MonitoringInfo CreateFile(const char* path) + { + BString absolutePath(test_path(path)); + FATAL_IF_ERROR( + BFile().SetTo(absolutePath, B_CREATE_FILE | B_READ_WRITE), + "Failed to create file \"%s\"", absolutePath.String()); + return MonitoringInfo(B_ENTRY_CREATED, absolutePath); + } + + MonitoringInfo MoveEntry(const char* fromPath, const char* toPath) + { + BString absoluteFromPath(test_path(fromPath)); + BString absoluteToPath(test_path(toPath)); + FATAL_IF_POSIX_ERROR(rename(absoluteFromPath, absoluteToPath), + "Failed to move \"%s\" to \"%s\"", absoluteFromPath.String(), + absoluteToPath.String()); + return MonitoringInfo(B_ENTRY_MOVED, absoluteFromPath, absoluteToPath); + } + + MonitoringInfo RemoveEntry(const char* path) + { + BString absolutePath(test_path(path)); + MonitoringInfo info(B_ENTRY_REMOVED, absolutePath); + BEntry entry; + FATAL_IF_ERROR(entry.SetTo(absolutePath), + "Failed to init BEntry for \"%s\"", absolutePath.String()); + FATAL_IF_ERROR(entry.Remove(), + "Failed to remove entry \"%s\"", absolutePath.String()); + return info; + } + + MonitoringInfo TouchEntry(const char* path) + { + BString absolutePath(test_path(path)); + FATAL_IF_POSIX_ERROR(utimes(absolutePath, NULL), + "Failed to touch \"%s\"", absolutePath.String()); + MonitoringInfo info(B_STAT_CHANGED, absolutePath); + return info; + } + + void ExpectNotification(const MonitoringInfo& info, bool expected = true) + { + if (!expected) + return; + + AutoLocker locker(this); + if (fNotifications.IsEmpty()) { + locker.Unlock(); + snooze(kMaxNotificationDelay); + locker.Lock(); + } + + if (fNotifications.IsEmpty()) { + FAIL("missing notification, expected:\n %s", + info.ToString().String()); + } + + BMessage* message = fNotifications.RemoveItemAt(0); + ObjectDeleter messageDeleter(message); + + if (!info.Matches(*message)) { + BString processedInfosString(_ProcessedInfosString()); + FAIL("unexpected notification:\n expected:\n %s\n got:\n%s%s", + info.ToString().String(), + indented_string(message_to_string(*message), " ").String(), + processedInfosString.String()); + } + + fProcessedMonitoringInfos.Add(info); + } + + void ExpectNotifications(MonitoringInfoSet infos) + { + bool waited = false; + AutoLocker locker(this); + + while (!infos.IsEmpty()) { + if (fNotifications.IsEmpty()) { + locker.Unlock(); + if (!waited) { + snooze(kMaxNotificationDelay); + waited = true; + } + locker.Lock(); + } + + if (fNotifications.IsEmpty()) { + FAIL("missing notification(s), expected:\n%s", + indented_string(infos.ToString(), " ").String()); + } + + BMessage* message = fNotifications.RemoveItemAt(0); + ObjectDeleter messageDeleter(message); + + bool foundMatch = false; + for (int32 i = 0; i < infos.CountInfos(); i++) { + const MonitoringInfo& info = infos.InfoAt(i); + if (info.Matches(*message)) { + infos.Remove(i); + foundMatch = true; + break; + } + } + + if (foundMatch) + continue; + + BString processedInfosString(_ProcessedInfosString()); + FAIL("unexpected notification:\n expected:\n%s\n got:\n%s%s", + indented_string(infos.ToString(), " ").String(), + indented_string(message_to_string(*message), " ").String(), + processedInfosString.String()); + } + } + + virtual void DoInternal(bool directoriesOnly, bool filesOnly, + bool recursive, bool pathOnly, bool watchStat) = 0; + +private: + typedef BObjectList MessageList; + typedef BObjectList MonitoringInfoList; + +private: + virtual void MessageReceived(BMessage* message) + { + switch (message->what) { + case B_PATH_MONITOR: + if (!fNotifications.AddItem(new BMessage(*message))) + FATAL_IF_ERROR(B_NO_MEMORY, "Failed to store notification"); + break; + + default: + BLooper::MessageReceived(message); + break; + } + } + +private: + void _CreateDirectory(const char* path) + { + FATAL_IF_ERROR(create_directory(path, 0755), + "Failed to create directory \"%s\"", path); + } + + void _RemoveRecursively(BEntry& entry) + { + // recurse, if the entry is a directory + if (entry.IsDirectory()) { + BDirectory directory; + FATAL_IF_ERROR(directory.SetTo(&entry), + "Failed to init BDirectory for \"%s\"", + BPath(&entry).Path()); + + BEntry childEntry; + while (directory.GetNextEntry(&childEntry) == B_OK) + _RemoveRecursively(childEntry); + } + + // remove the entry + FATAL_IF_ERROR(entry.Remove(), "Failed to remove entry \"%s\"", + BPath(&entry).Path()); + } + + BString _ProcessedInfosString() const + { + BString processedInfosString; + if (!fProcessedMonitoringInfos.IsEmpty()) { + processedInfosString << "\nprocessed so far:\n" + << indented_string(fProcessedMonitoringInfos.ToString(), " "); + } + return processedInfosString; + } + +protected: + BString fName; + uint32 fFlags; + thread_id fLooperThread; + MessageList fNotifications; + MonitoringInfoSet fProcessedMonitoringInfos; + bool fIsWatching; +}; + + +struct TestBase : Test { +protected: + TestBase(const char* name) + : + Test(name) + { + } + + void StandardSetup() + { + CreateDirectory("base"); + CreateDirectory("base/dir1"); + CreateDirectory("base/dir1/dir0"); + CreateFile("base/file0"); + CreateFile("base/dir1/file0.0"); + } +}; + + +#define CREATE_TEST_WITH_CUSTOM_SETUP(name, code) \ + struct Test##name : TestBase { \ + Test##name() : TestBase(#name) {} \ + virtual void DoInternal(bool directoriesOnly, bool filesOnly, \ + bool recursive, bool pathOnly, bool watchStat) \ + { \ + code \ + } \ + }; \ + tests.push_back(new Test##name); + +#define CREATE_TEST(name, code) \ + CREATE_TEST_WITH_CUSTOM_SETUP(name, \ + StandardSetup(); \ + StartWatching("base"); \ + code \ + ) + + +static void +create_tests(std::vector& tests) +{ + // test coverage: + // - file/directory outside + // - file/directory at top level + // - file/directory at sub level + // - move file/directory into/within/out of + // - move non-empty directory into/within/out of + // - create/move ancestor folder + // - remove/move ancestor folder + // - touch path, file/directory at top and sub level + // - base file instead of directory + // + // not covered (yet): + // - mount/unmount below/in our path + // - test symlink in watched path + // - attribute watching (should be similar to stat watching) + + CREATE_TEST(FileOutside, + CreateFile("file1"); + MoveEntry("file1", "file2"); + RemoveEntry("file2"); + ) + + CREATE_TEST(DirectoryOutside, + CreateDirectory("dir1"); + MoveEntry("dir1", "dir2"); + RemoveEntry("dir2"); + ) + + CREATE_TEST(FileTopLevel, + ExpectNotification(CreateFile("base/file1"), + !directoriesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/file1", "base/file2"), + !directoriesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/file2"), + !directoriesOnly && !pathOnly); + ) + + CREATE_TEST(DirectoryTopLevel, + ExpectNotification(CreateDirectory("base/dir2"), + !filesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/dir2", "base/dir3"), + !filesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir3"), + !filesOnly && !pathOnly); + ) + + CREATE_TEST(FileSubLevel, + ExpectNotification(CreateFile("base/dir1/file1"), + recursive && !directoriesOnly); + ExpectNotification(MoveEntry("base/dir1/file1", "base/dir1/file2"), + recursive && !directoriesOnly); + ExpectNotification(RemoveEntry("base/dir1/file2"), + recursive && !directoriesOnly); + ) + + CREATE_TEST(DirectorySubLevel, + ExpectNotification(CreateDirectory("base/dir1/dir2"), + recursive && !filesOnly); + ExpectNotification(MoveEntry("base/dir1/dir2", "base/dir1/dir3"), + recursive && !filesOnly); + ExpectNotification(RemoveEntry("base/dir1/dir3"), + recursive && !filesOnly); + ) + + CREATE_TEST(FileMoveIntoTopLevel, + CreateFile("file1"); + ExpectNotification(MoveEntry("file1", "base/file2"), + !directoriesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/file2"), + !directoriesOnly && !pathOnly); + ) + + CREATE_TEST(DirectoryMoveIntoTopLevel, + CreateDirectory("dir2"); + ExpectNotification(MoveEntry("dir2", "base/dir3"), + !filesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir3"), + !filesOnly && !pathOnly); + ) + + CREATE_TEST(FileMoveIntoSubLevel, + CreateFile("file1"); + ExpectNotification(MoveEntry("file1", "base/dir1/file2"), + recursive && !directoriesOnly); + ExpectNotification(RemoveEntry("base/dir1/file2"), + recursive && !directoriesOnly); + ) + + CREATE_TEST(DirectoryMoveIntoSubLevel, + CreateDirectory("dir2"); + ExpectNotification(MoveEntry("dir2", "base/dir1/dir3"), + recursive && !filesOnly); + ExpectNotification(RemoveEntry("base/dir1/dir3"), + recursive && !filesOnly); + ) + + CREATE_TEST(FileMoveOutOfTopLevel, + ExpectNotification(CreateFile("base/file1"), + !directoriesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/file1", "file2"), + !directoriesOnly && !pathOnly); + RemoveEntry("file2"); + ) + + CREATE_TEST(DirectoryMoveOutOfTopLevel, + ExpectNotification(CreateDirectory("base/dir2"), + !filesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/dir2", "dir3"), + !filesOnly && !pathOnly); + RemoveEntry("dir3"); + ) + + CREATE_TEST(FileMoveOutOfSubLevel, + ExpectNotification(CreateFile("base/dir1/file1"), + recursive && !directoriesOnly); + ExpectNotification(MoveEntry("base/dir1/file1", "file2"), + recursive && !directoriesOnly); + RemoveEntry("file2"); + ) + + CREATE_TEST(DirectoryMoveOutOfSubLevel, + ExpectNotification(CreateDirectory("base/dir1/dir2"), + recursive && !filesOnly); + ExpectNotification(MoveEntry("base/dir1/dir2", "dir3"), + recursive && !filesOnly); + RemoveEntry("dir3"); + ) + + CREATE_TEST(FileMoveToTopLevel, + ExpectNotification(CreateFile("base/dir1/file1"), + !directoriesOnly && recursive); + ExpectNotification(MoveEntry("base/dir1/file1", "base/file2"), + !directoriesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/file2"), + !directoriesOnly && !pathOnly); + ) + + CREATE_TEST(DirectoryMoveToTopLevel, + ExpectNotification(CreateDirectory("base/dir1/dir2"), + !filesOnly && recursive); + ExpectNotification(MoveEntry("base/dir1/dir2", "base/dir3"), + !filesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir3"), + !filesOnly && !pathOnly); + ) + + CREATE_TEST(FileMoveToSubLevel, + ExpectNotification(CreateFile("base/file1"), + !directoriesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/file1", "base/dir1/file2"), + !directoriesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir1/file2"), + !directoriesOnly && recursive); + ) + + CREATE_TEST(DirectoryMoveToSubLevel, + ExpectNotification(CreateDirectory("base/dir2"), + !filesOnly && !pathOnly); + ExpectNotification(MoveEntry("base/dir2", "base/dir1/dir3"), + !filesOnly && !pathOnly); + ExpectNotification(RemoveEntry("base/dir1/dir3"), + !filesOnly && recursive); + ) + + CREATE_TEST(NonEmptyDirectoryMoveIntoTopLevel, + CreateDirectory("dir2"); + CreateDirectory("dir2/dir3"); + CreateDirectory("dir2/dir4"); + CreateFile("dir2/file1"); + CreateFile("dir2/dir3/file2"); + ExpectNotification(MoveEntry("dir2", "base/dir5"), + !filesOnly && !pathOnly); + if (recursive && filesOnly) { + ExpectNotifications(MonitoringInfoSet() + .Add(B_ENTRY_CREATED, "base/dir5/file1") + .Add(B_ENTRY_CREATED, "base/dir5/dir3/file2")); + } + ) + + CREATE_TEST(NonEmptyDirectoryMoveIntoSubLevel, + CreateDirectory("dir2"); + CreateDirectory("dir2/dir3"); + CreateDirectory("dir2/dir4"); + CreateFile("dir2/file1"); + CreateFile("dir2/dir3/file2"); + ExpectNotification(MoveEntry("dir2", "base/dir1/dir5"), + !filesOnly && recursive); + if (recursive && filesOnly) { + ExpectNotifications(MonitoringInfoSet() + .Add(B_ENTRY_CREATED, "base/dir1/dir5/file1") + .Add(B_ENTRY_CREATED, "base/dir1/dir5/dir3/file2")); + } + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveOutOfTopLevel, + StandardSetup(); + CreateDirectory("base/dir2"); + CreateDirectory("base/dir2/dir3"); + CreateDirectory("base/dir2/dir4"); + CreateFile("base/dir2/file1"); + CreateFile("base/dir2/dir3/file2"); + StartWatching("base"); + MonitoringInfoSet filesRemoved; + if (recursive && filesOnly) { + filesRemoved + .Add(B_ENTRY_REMOVED, "base/dir2/file1") + .Add(B_ENTRY_REMOVED, "base/dir2/dir3/file2"); + } + ExpectNotification(MoveEntry("base/dir2", "dir5"), + !filesOnly && !pathOnly); + ExpectNotifications(filesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveOutOfSubLevel, + StandardSetup(); + CreateDirectory("base/dir1/dir2"); + CreateDirectory("base/dir1/dir2/dir3"); + CreateDirectory("base/dir1/dir2/dir4"); + CreateFile("base/dir1/dir2/file1"); + CreateFile("base/dir1/dir2/dir3/file2"); + StartWatching("base"); + MonitoringInfoSet filesRemoved; + if (recursive && filesOnly) { + filesRemoved + .Add(B_ENTRY_REMOVED, "base/dir1/dir2/file1") + .Add(B_ENTRY_REMOVED, "base/dir1/dir2/dir3/file2"); + } + ExpectNotification(MoveEntry("base/dir1/dir2", "dir5"), + !filesOnly && recursive); + ExpectNotifications(filesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveToTopLevel, + StandardSetup(); + CreateDirectory("base/dir1/dir2"); + CreateDirectory("base/dir1/dir2/dir3"); + CreateDirectory("base/dir1/dir2/dir4"); + CreateFile("base/dir1/dir2/file1"); + CreateFile("base/dir1/dir2/dir3/file2"); + StartWatching("base"); + MonitoringInfoSet filesMoved; + if (recursive && filesOnly) { + filesMoved + .Add(B_ENTRY_REMOVED, "base/dir1/dir2/file1") + .Add(B_ENTRY_REMOVED, "base/dir1/dir2/dir3/file2"); + } + ExpectNotification(MoveEntry("base/dir1/dir2", "base/dir5"), + !filesOnly && !pathOnly); + if (recursive && filesOnly) { + filesMoved + .Add(B_ENTRY_CREATED, "base/dir5/file1") + .Add(B_ENTRY_CREATED, "base/dir5/dir3/file2"); + } + ExpectNotifications(filesMoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveToSubLevel, + StandardSetup(); + CreateDirectory("base/dir2"); + CreateDirectory("base/dir2/dir3"); + CreateDirectory("base/dir2/dir4"); + CreateFile("base/dir2/file1"); + CreateFile("base/dir2/dir3/file2"); + StartWatching("base"); + MonitoringInfoSet filesMoved; + if (recursive && filesOnly) { + filesMoved + .Add(B_ENTRY_REMOVED, "base/dir2/file1") + .Add(B_ENTRY_REMOVED, "base/dir2/dir3/file2"); + } + ExpectNotification(MoveEntry("base/dir2", "base/dir1/dir5"), + !filesOnly && !pathOnly); + if (recursive && filesOnly) { + filesMoved + .Add(B_ENTRY_CREATED, "base/dir1/dir5/file1") + .Add(B_ENTRY_CREATED, "base/dir1/dir5/dir3/file2"); + } + ExpectNotifications(filesMoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(CreateAncestor, + StartWatching("ancestor/base"); + CreateDirectory("ancestor"); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestor, + CreateDirectory("ancestorSibling"); + StartWatching("ancestor/base"); + MoveEntry("ancestorSibling", "ancestor"); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestorWithBase, + CreateDirectory("ancestorSibling"); + CreateDirectory("ancestorSibling/base"); + StartWatching("ancestor/base"); + MoveEntry("ancestorSibling", "ancestor"); + MonitoringInfoSet entriesCreated; + if (!filesOnly) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestorWithBaseAndFile, + CreateDirectory("ancestorSibling"); + CreateDirectory("ancestorSibling/base"); + CreateFile("ancestorSibling/base/file1"); + StartWatching("ancestor/base"); + MoveEntry("ancestorSibling", "ancestor"); + MonitoringInfoSet entriesCreated; + if (!filesOnly) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base"); + else if (!pathOnly) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/file1"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestorWithBaseAndDirectory, + CreateDirectory("ancestorSibling"); + CreateDirectory("ancestorSibling/base"); + CreateDirectory("ancestorSibling/base/dir1"); + CreateFile("ancestorSibling/base/dir1/file1"); + StartWatching("ancestor/base"); + MoveEntry("ancestorSibling", "ancestor"); + MonitoringInfoSet entriesCreated; + if (!filesOnly) { + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base"); + } else if (recursive) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/dir1/file1"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(CreateBase, + CreateDirectory("ancestor"); + StartWatching("ancestor/base"); + ExpectNotification(CreateDirectory("ancestor/base"), + !filesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateBase, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/baseSibling"); + StartWatching("ancestor/base"); + ExpectNotification(MoveEntry("ancestor/baseSibling", "ancestor/base"), + !filesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateBaseWithFile, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/baseSibling"); + CreateFile("ancestor/baseSibling/file1"); + StartWatching("ancestor/base"); + ExpectNotification(MoveEntry("ancestor/baseSibling", "ancestor/base"), + !filesOnly); + MonitoringInfoSet entriesCreated; + if (filesOnly && !pathOnly) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/file1"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateBaseWithDirectory, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/baseSibling"); + CreateDirectory("ancestor/baseSibling/dir1"); + CreateFile("ancestor/baseSibling/dir1/file1"); + StartWatching("ancestor/base"); + ExpectNotification(MoveEntry("ancestor/baseSibling", "ancestor/base"), + !filesOnly); + MonitoringInfoSet entriesCreated; + if (filesOnly && recursive) + entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/dir1/file1"); + ExpectNotifications(entriesCreated); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveAncestorWithBaseAndFile, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/base"); + CreateFile("ancestor/base/file1"); + StartWatching("ancestor/base"); + MonitoringInfoSet entriesRemoved; + if (!filesOnly) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base"); + else if (!pathOnly) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/file1"); + MoveEntry("ancestor", "ancestorSibling"); + ExpectNotifications(entriesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveAncestorWithBaseAndDirectory, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/base"); + CreateDirectory("ancestor/base/dir1"); + CreateFile("ancestor/base/dir1/file1"); + StartWatching("ancestor/base"); + MonitoringInfoSet entriesRemoved; + if (!filesOnly) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base"); + else if (recursive) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/dir1/file1"); + MoveEntry("ancestor", "ancestorSibling"); + ExpectNotifications(entriesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveBaseWithFile, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/base"); + CreateFile("ancestor/base/file1"); + StartWatching("ancestor/base"); + MonitoringInfoSet entriesRemoved; + if (filesOnly && !pathOnly) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/file1"); + ExpectNotification(MoveEntry("ancestor/base", "ancestor/baseSibling"), + !filesOnly); + ExpectNotifications(entriesRemoved); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveBaseWithDirectory, + CreateDirectory("ancestor"); + CreateDirectory("ancestor/base"); + CreateDirectory("ancestor/base/dir1"); + CreateFile("ancestor/base/dir1/file1"); + StartWatching("ancestor/base"); + MonitoringInfoSet entriesRemoved; + if (filesOnly && recursive) + entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/dir1/file1"); + ExpectNotification(MoveEntry("ancestor/base", "ancestor/baseSibling"), + !filesOnly); + ExpectNotifications(entriesRemoved); + ) + + CREATE_TEST(TouchBase, + ExpectNotification(TouchEntry("base"), watchStat && !filesOnly); + ) + + CREATE_TEST(TouchFileTopLevel, + ExpectNotification(TouchEntry("base/file0"), + watchStat && recursive && !directoriesOnly); + ) + + CREATE_TEST(TouchFileSubLevel, + ExpectNotification(TouchEntry("base/dir1/file0.0"), + watchStat && recursive && !directoriesOnly); + ) + + CREATE_TEST(TouchDirectoryTopLevel, + ExpectNotification(TouchEntry("base/dir1"), + watchStat && recursive && !filesOnly); + ) + + CREATE_TEST(TouchDirectorySubLevel, + ExpectNotification(TouchEntry("base/dir1/dir0"), + watchStat && recursive && !filesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(CreateFileBase, + StartWatching("file"); + ExpectNotification(CreateFile("file"), + !directoriesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateFileBase, + CreateFile("fileSibling"); + StartWatching("file"); + ExpectNotification(MoveEntry("fileSibling", "file"), + !directoriesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(RemoveFileBase, + CreateFile("file"); + StartWatching("file"); + ExpectNotification(RemoveEntry("file"), + !directoriesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveFileBase, + CreateFile("file"); + StartWatching("file"); + ExpectNotification(MoveEntry("file", "fileSibling"), + !directoriesOnly); + ) + + CREATE_TEST_WITH_CUSTOM_SETUP(TouchFileBase, + CreateFile("file"); + StartWatching("file"); + ExpectNotification(TouchEntry("file"), + watchStat && !directoriesOnly); + ) +} + + +static void +run_tests(std::set testNames, uint32 watchFlags, + size_t& totalTests, size_t& succeededTests) +{ + std::vector tests; + create_tests(tests); + + // filter the tests, if test names have been specified + size_t testCount = tests.size(); + if (!testNames.empty()) { + for (size_t i = 0; i < testCount;) { + Test* test = tests[i]; + std::set::iterator it = testNames.find(test->Name()); + if (it != testNames.end()) { + testNames.erase(it); + i++; + } else { + tests.erase(tests.begin() + i); + test->Delete(); + testCount--; + } + } + + if (!testNames.empty()) { + printf("no such test(s):\n"); + for (std::set::iterator it = testNames.begin(); + it != testNames.end(); ++it) { + printf(" %s\n", it->String()); + exit(1); + } + } + } + + printf("\nrunning tests with flags: %s\n", + watch_flags_to_string(watchFlags).String()); + + int32 longestTestName = 0; + + for (size_t i = 0; i < testCount; i++) { + Test* test = tests[i]; + longestTestName = std::max(longestTestName, test->Name().Length()); + } + + for (size_t i = 0; i < testCount; i++) { + Test* test = tests[i]; + bool terminate = false; + + try { + totalTests++; + test->Init(watchFlags); + printf(" %s: %*s", test->Name().String(), + int(longestTestName - test->Name().Length()), ""); + fflush(stdout); + test->Do(); + printf("SUCCEEDED\n"); + succeededTests++; + } catch (FatalException& exception) { + printf("FAILED FATALLY\n"); + printf("%s\n", + indented_string(exception.Message(), " ").String()); + terminate = true; + } catch (TestException& exception) { + printf("FAILED\n"); + printf("%s\n", + indented_string(exception.Message(), " ").String()); + } + + test->Delete(); + + if (terminate) + exit(1); + } +} + + +int +main(int argc, const char* const* argv) +{ + // any args are test names + std::set testNames; + for (int i = 1; i < argc; i++) + testNames.insert(argv[i]); + + // flags that can be combined arbitrarily + const uint32 kFlags[] = { + B_WATCH_NAME, + B_WATCH_STAT, + // not that interesting, since similar to B_WATCH_STAT: B_WATCH_ATTR + B_WATCH_DIRECTORY, + B_WATCH_RECURSIVELY, + }; + const size_t kFlagCount = sizeof(kFlags) / sizeof(kFlags[0]); + + size_t totalTests = 0; + size_t succeededTests = 0; + + for (size_t i = 0; i < 1 << kFlagCount; i++) { + // construct flags mask + uint32 flags = 0; + for (size_t k = 0; k < kFlagCount; k++) { + if ((i & (1 << k)) != 0) + flags |= kFlags[k]; + } + + // run tests -- in recursive mode do that additionally for the mutually + // B_WATCH_FILES_ONLY and B_WATCH_DIRECTORIES_ONLY flags. + run_tests(testNames, flags, totalTests, succeededTests); + if ((flags & B_WATCH_RECURSIVELY) != 0) { + run_tests(testNames, flags | B_WATCH_FILES_ONLY, totalTests, + succeededTests); + run_tests(testNames, flags | B_WATCH_DIRECTORIES_ONLY, totalTests, + succeededTests); + } + } + + printf("\n"); + if (succeededTests == totalTests) { + printf("ALL TESTS SUCCEEDED\n"); + } else { + printf("%zu of %zu TESTS FAILED\n", totalTests - succeededTests, + totalTests); + } + + return 0; +}