/* * 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; }