1/*
2 * Copyright 2013, Haiku, Inc.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Ingo Weinhold, ingo_weinhold@gmx.de
7 */
8
9
10#include <errno.h>
11#include <stdio.h>
12#include <string.h>
13#include <sys/time.h>
14
15#include <algorithm>
16#include <set>
17#include <vector>
18
19#include <Directory.h>
20#include <Entry.h>
21#include <File.h>
22#include <Looper.h>
23#include <ObjectList.h>
24#include <Path.h>
25#include <String.h>
26
27#include <AutoDeleter.h>
28#include <AutoLocker.h>
29#include <NotOwningEntryRef.h>
30#include <PathMonitor.h>
31
32
33using BPrivate::BPathMonitor;
34
35
36static const char* const kTestBasePath = "/tmp/path-monitor-test";
37static const bigtime_t kMaxNotificationDelay = 100000;
38
39
40#define FATAL(...)													\
41	do {															\
42		throw FatalException(										\
43			BString().SetToFormat("%s:%d: ", __FILE__, __LINE__)	\
44				<< BString().SetToFormat(__VA_ARGS__));				\
45	} while (false)
46
47#define FATAL_IF_ERROR(error, ...)										\
48	do {																\
49		status_t _fatalError = (error);									\
50		if (_fatalError < 0) {											\
51			throw FatalException(										\
52				BString().SetToFormat("%s:%d: ", __FILE__, __LINE__)	\
53					<< BString().SetToFormat(__VA_ARGS__)				\
54					<< BString().SetToFormat(							\
55						": %s\n", strerror(_fatalError)));				\
56		}																\
57	} while (false)
58
59#define FATAL_IF_POSIX_ERROR(error, ...)	\
60	if ((error) < 0)						\
61		FATAL_IF_ERROR(errno, __VA_ARGS__)
62
63#define FAIL(...)	\
64	throw TestException(BString().SetToFormat(__VA_ARGS__))
65
66
67struct TestException {
68	TestException(const BString& message)
69		:
70		fMessage(message)
71	{
72	}
73
74	const BString& Message() const
75	{
76		return fMessage;
77	}
78
79private:
80	BString	fMessage;
81};
82
83
84struct FatalException {
85	FatalException(const BString& message)
86		:
87		fMessage(message)
88	{
89	}
90
91	const BString& Message() const
92	{
93		return fMessage;
94	}
95
96private:
97	BString	fMessage;
98};
99
100
101static BString
102test_path(const BString& maybeRelativePath)
103{
104	if (maybeRelativePath.ByteAt(0) == '/')
105		return maybeRelativePath;
106
107	BString path;
108	path.SetToFormat("%s/%s", kTestBasePath, maybeRelativePath.String());
109	if (path.IsEmpty())
110		FATAL_IF_ERROR(B_NO_MEMORY, "Failed to make absolute path");
111	return path;
112}
113
114
115static BString
116node_ref_to_string(const node_ref& nodeRef)
117{
118	return BString().SetToFormat("%" B_PRIdDEV ":%" B_PRIdINO, nodeRef.device,
119		nodeRef.node);
120}
121
122
123static BString
124entry_ref_to_string(const entry_ref& entryRef)
125{
126	return BString().SetToFormat("%" B_PRIdDEV ":%" B_PRIdINO ":\"%s\"",
127		entryRef.device, entryRef.directory, entryRef.name);
128}
129
130
131static BString
132indented_string(const char* string, const char* indent,
133	const char* firstIndent = NULL)
134{
135	const char* end = string + strlen(string);
136	BString result;
137	const char* line = string;
138	while (line < end) {
139		const char* lineEnd = strchr(line, '\n');
140		lineEnd = lineEnd != NULL ? lineEnd + 1 : end;
141		result
142			<< (line == string && firstIndent != NULL ? firstIndent : indent);
143		result.Append(line, lineEnd - line);
144		line = lineEnd;
145	}
146
147	return result;
148}
149
150
151static BString
152message_to_string(const BMessage& message)
153{
154	BString result;
155
156	char* name;
157	type_code typeCode;
158	int32 count;
159	for (int32 i = 0;
160		message.GetInfo(B_ANY_TYPE, i, &name, &typeCode, &count) == B_OK;
161		i++) {
162		if (i > 0)
163			result << '\n';
164
165		result << '"' << name << '"';
166		BString type;
167
168		switch (typeCode) {
169			case B_UINT8_TYPE:
170			case B_INT8_TYPE:
171				type << "int8";
172				break;
173
174			case B_UINT16_TYPE:
175				type = "u";
176			case B_INT16_TYPE:
177				type << "int16";
178				break;
179
180			case B_UINT32_TYPE:
181				type = "u";
182			case B_INT32_TYPE:
183				type << "int32";
184				break;
185
186			case B_UINT64_TYPE:
187				type = "u";
188			case B_INT64_TYPE:
189				type << "int64";
190				break;
191
192			case B_STRING_TYPE:
193				type = "string";
194				break;
195
196			default:
197			{
198				int code = (int)typeCode;
199				type.SetToFormat("'%02x%02x%02x%02x'", code >> 24,
200					(code >> 16) & 0xff, (code >> 8) & 0xff, code & 0xff);
201				break;
202			}
203		}
204
205		result << " (" << type << "):";
206
207		for (int32 k = 0; k < count; k++) {
208			BString value;
209			switch (typeCode) {
210				case B_UINT8_TYPE:
211					value << message.GetUInt8(name, k, 0);
212					break;
213				case B_INT8_TYPE:
214					value << message.GetInt8(name, k, 0);
215					break;
216				case B_UINT16_TYPE:
217					value << message.GetUInt16(name, k, 0);
218					break;
219				case B_INT16_TYPE:
220					value << message.GetInt16(name, k, 0);
221					break;
222				case B_UINT32_TYPE:
223					value << message.GetUInt32(name, k, 0);
224					break;
225				case B_INT32_TYPE:
226					value << message.GetInt32(name, k, 0);
227					break;
228				case B_UINT64_TYPE:
229					value << message.GetUInt64(name, k, 0);
230					break;
231				case B_INT64_TYPE:
232					value << message.GetInt64(name, k, 0);
233					break;
234				case B_STRING_TYPE:
235					value.SetToFormat("\"%s\"", message.GetString(name, k, ""));
236					break;
237				default:
238				{
239					const void* data;
240					ssize_t size;
241					if (message.FindData(name, typeCode, k, &data, &size)
242							!= B_OK) {
243						value = "???";
244						break;
245					}
246
247					for (ssize_t l = 0; l < size; l++) {
248						uint8 v = ((const uint8*)data)[l];
249						value << BString().SetToFormat("%02x", v);
250					}
251					break;
252				}
253			}
254
255			if (k == 0 && count == 1) {
256				result << ' ' << value;
257			} else {
258				result << BString().SetToFormat("\n  [%2" B_PRId32 "] ", k)
259					<< value;
260			}
261		}
262	}
263
264	return result;
265}
266
267
268static BString
269watch_flags_to_string(uint32 flags)
270{
271	BString result;
272	if ((flags & B_WATCH_NAME) != 0)
273		result << "name ";
274	if ((flags & B_WATCH_STAT) != 0)
275		result << "stat ";
276	if ((flags & B_WATCH_ATTR) != 0)
277		result << "attr ";
278	if ((flags & B_WATCH_DIRECTORY) != 0)
279		result << "dir ";
280	if ((flags & B_WATCH_RECURSIVELY) != 0)
281		result << "recursive ";
282	if ((flags & B_WATCH_FILES_ONLY) != 0)
283		result << "files-only ";
284	if ((flags & B_WATCH_DIRECTORIES_ONLY) != 0)
285		result << "dirs-only ";
286
287	if (!result.IsEmpty())
288		result.Truncate(result.Length() - 1);
289	return result;
290}
291
292
293struct MonitoringInfo {
294	MonitoringInfo()
295	{
296	}
297
298	MonitoringInfo(int32 opcode, const char* path)
299		:
300		fOpcode(opcode)
301	{
302		_Init(opcode, path);
303	}
304
305	MonitoringInfo(int32 opcode, const char* fromPath, const char* toPath)
306	{
307		_Init(opcode, toPath);
308
309		// init fFromEntryRef
310		BEntry entry;
311		FATAL_IF_ERROR(entry.SetTo(fromPath),
312			"Failed to init BEntry for \"%s\"", fromPath);
313		FATAL_IF_ERROR(entry.GetRef(&fFromEntryRef),
314			"Failed to get entry_ref for \"%s\"", fromPath);
315	}
316
317	BString ToString() const
318	{
319		switch (fOpcode) {
320			case B_ENTRY_CREATED:
321			case B_ENTRY_REMOVED:
322				return BString().SetToFormat("%s %s at %s",
323					fOpcode == B_ENTRY_CREATED ? "created" : "removed",
324					node_ref_to_string(fNodeRef).String(),
325					entry_ref_to_string(fEntryRef).String());
326
327			case B_ENTRY_MOVED:
328				return BString().SetToFormat("moved %s from %s to %s",
329					node_ref_to_string(fNodeRef).String(),
330					entry_ref_to_string(fFromEntryRef).String(),
331					entry_ref_to_string(fEntryRef).String());
332
333			case B_STAT_CHANGED:
334				return BString().SetToFormat("stat changed for %s",
335					node_ref_to_string(fNodeRef).String());
336
337			case B_ATTR_CHANGED:
338				return BString().SetToFormat("attr changed for %s",
339					node_ref_to_string(fNodeRef).String());
340
341			case B_DEVICE_MOUNTED:
342				return BString().SetToFormat("volume mounted");
343
344			case B_DEVICE_UNMOUNTED:
345				return BString().SetToFormat("volume unmounted");
346		}
347
348		return BString();
349	}
350
351	bool Matches(const BMessage& message) const
352	{
353		if (fOpcode != message.GetInt32("opcode", -1))
354			return false;
355
356		switch (fOpcode) {
357			case B_ENTRY_CREATED:
358			case B_ENTRY_REMOVED:
359			{
360				NotOwningEntryRef entryRef;
361				node_ref nodeRef;
362
363				if (message.FindInt32("device", &nodeRef.device) != B_OK
364					|| message.FindInt64("node", &nodeRef.node) != B_OK
365					|| message.FindInt64("directory", &entryRef.directory)
366						!= B_OK
367					|| message.FindString("name", (const char**)&entryRef.name)
368						!= B_OK) {
369					return false;
370				}
371				entryRef.device = nodeRef.device;
372
373				return nodeRef == fNodeRef && entryRef == fEntryRef;
374			}
375
376			case B_ENTRY_MOVED:
377			{
378				NotOwningEntryRef fromEntryRef;
379				NotOwningEntryRef toEntryRef;
380				node_ref nodeRef;
381
382				if (message.FindInt32("node device", &nodeRef.device) != B_OK
383					|| message.FindInt64("node", &nodeRef.node) != B_OK
384					|| message.FindInt32("device", &fromEntryRef.device)
385						!= B_OK
386					|| message.FindInt64("from directory",
387						&fromEntryRef.directory) != B_OK
388					|| message.FindInt64("to directory", &toEntryRef.directory)
389						!= B_OK
390					|| message.FindString("from name",
391						(const char**)&fromEntryRef.name) != B_OK
392					|| message.FindString("name",
393						(const char**)&toEntryRef.name) != B_OK) {
394					return false;
395				}
396				toEntryRef.device = fromEntryRef.device;
397
398				return nodeRef == fNodeRef && toEntryRef == fEntryRef
399					&& fromEntryRef == fFromEntryRef;
400			}
401
402			case B_STAT_CHANGED:
403			case B_ATTR_CHANGED:
404			{
405				node_ref nodeRef;
406
407				if (message.FindInt32("device", &nodeRef.device) != B_OK
408					|| message.FindInt64("node", &nodeRef.node) != B_OK) {
409					return false;
410				}
411
412				return nodeRef == fNodeRef;
413			}
414
415			case B_DEVICE_MOUNTED:
416			case B_DEVICE_UNMOUNTED:
417				return true;
418		}
419
420		return false;
421	}
422
423private:
424	void _Init(int32 opcode, const char* path)
425	{
426		fOpcode = opcode;
427		BEntry entry;
428		FATAL_IF_ERROR(entry.SetTo(path), "Failed to init BEntry for \"%s\"",
429			path);
430		FATAL_IF_ERROR(entry.GetRef(&fEntryRef),
431			"Failed to get entry_ref for \"%s\"", path);
432		FATAL_IF_ERROR(entry.GetNodeRef(&fNodeRef),
433			"Failed to get node_ref for \"%s\"", path);
434	}
435
436private:
437	int32		fOpcode;
438	node_ref	fNodeRef;
439	entry_ref	fEntryRef;
440	entry_ref	fFromEntryRef;
441};
442
443
444struct MonitoringInfoSet {
445	MonitoringInfoSet()
446	{
447	}
448
449	MonitoringInfoSet& Add(const MonitoringInfo& info, bool expected = true)
450	{
451		if (expected)
452			fInfos.push_back(info);
453		return *this;
454	}
455
456	MonitoringInfoSet& Add(int32 opcode, const BString& path,
457		bool expected = true)
458	{
459		return Add(MonitoringInfo(opcode, test_path(path)), expected);
460	}
461
462	MonitoringInfoSet& Add(int32 opcode, const BString& fromPath,
463		const BString& toPath, bool expected = true)
464	{
465		return Add(MonitoringInfo(opcode, test_path(fromPath),
466			test_path(toPath)), expected);
467	}
468
469	bool IsEmpty() const
470	{
471		return fInfos.empty();
472	}
473
474	int32 CountInfos() const
475	{
476		return fInfos.size();
477	}
478
479	const MonitoringInfo& InfoAt(int32 index) const
480	{
481		return fInfos[index];
482	}
483
484	void Remove(int32 index)
485	{
486		fInfos.erase(fInfos.begin() + index);
487	}
488
489	BString ToString() const
490	{
491		BString result;
492		for (int32 i = 0; i < CountInfos(); i++) {
493			const MonitoringInfo& info = InfoAt(i);
494			if (i > 0)
495				result << '\n';
496			result << info.ToString();
497		}
498		return result;
499	}
500
501private:
502	std::vector<MonitoringInfo>	fInfos;
503};
504
505
506struct Test : private BLooper {
507	Test(const char* name)
508		:
509		fName(name),
510		fFlags(0),
511		fLooperThread(-1),
512		fNotifications(10, true),
513		fProcessedMonitoringInfos(),
514		fIsWatching(false)
515	{
516	}
517
518	void Init(uint32 flags)
519	{
520		fFlags = flags;
521
522		// delete and re-create the test directory
523		BEntry entry;
524		FATAL_IF_ERROR(entry.SetTo(kTestBasePath),
525			"Failed to init entry to \"%s\"", kTestBasePath);
526
527		if (entry.Exists())
528			_RemoveRecursively(entry);
529
530		_CreateDirectory(kTestBasePath);
531
532		fLooperThread = BLooper::Run();
533		if (fLooperThread < 0)
534			FATAL_IF_ERROR(fLooperThread, "Failed to init looper");
535	}
536
537	void Delete()
538	{
539		if (fIsWatching)
540			BPathMonitor::StopWatching(this);
541
542		if (fLooperThread < 0) {
543			delete this;
544		} else {
545			PostMessage(B_QUIT_REQUESTED);
546			wait_for_thread(fLooperThread, NULL);
547		}
548	}
549
550	void Do()
551	{
552		bool recursive = (fFlags & B_WATCH_RECURSIVELY) != 0;
553		DoInternal(recursive && (fFlags & B_WATCH_DIRECTORIES_ONLY) != 0,
554			recursive && (fFlags & B_WATCH_FILES_ONLY) != 0, recursive,
555			!recursive && (fFlags & B_WATCH_DIRECTORY) == 0,
556			(fFlags & B_WATCH_STAT) != 0);
557
558		// verify that there aren't any spurious notifications
559		snooze(kMaxNotificationDelay);
560
561		AutoLocker<BLooper> locker(this);
562		if (fNotifications.IsEmpty())
563			return;
564
565		BString pendingNotifications
566			= "unexpected notification(s) at end of test:";
567		for (int32 i = 0; BMessage* message = fNotifications.ItemAt(i); i++) {
568			pendingNotifications << '\n'
569				<< indented_string(message_to_string(*message), "    ", "  * ");
570		}
571
572		FAIL("%s%s", pendingNotifications.String(),
573			_ProcessedInfosString().String());
574	}
575
576	const BString& Name() const
577	{
578		return fName;
579	}
580
581protected:
582	~Test()
583	{
584	}
585
586	void StartWatching(const char* path)
587	{
588		BString absolutePath(test_path(path));
589		FATAL_IF_ERROR(BPathMonitor::StartWatching(absolutePath, fFlags, this),
590			"Failed to start watching \"%s\"", absolutePath.String());
591		fIsWatching = true;
592	}
593
594	MonitoringInfo CreateDirectory(const char* path)
595	{
596		BString absolutePath(test_path(path));
597		_CreateDirectory(absolutePath);
598		return MonitoringInfo(B_ENTRY_CREATED, absolutePath);
599	}
600
601	MonitoringInfo CreateFile(const char* path)
602	{
603		BString absolutePath(test_path(path));
604		FATAL_IF_ERROR(
605			BFile().SetTo(absolutePath, B_CREATE_FILE | B_READ_WRITE),
606			"Failed to create file \"%s\"", absolutePath.String());
607		return MonitoringInfo(B_ENTRY_CREATED, absolutePath);
608	}
609
610	MonitoringInfo MoveEntry(const char* fromPath, const char* toPath)
611	{
612		BString absoluteFromPath(test_path(fromPath));
613		BString absoluteToPath(test_path(toPath));
614		FATAL_IF_POSIX_ERROR(rename(absoluteFromPath, absoluteToPath),
615			"Failed to move \"%s\" to \"%s\"", absoluteFromPath.String(),
616			absoluteToPath.String());
617		return MonitoringInfo(B_ENTRY_MOVED, absoluteFromPath, absoluteToPath);
618	}
619
620	MonitoringInfo RemoveEntry(const char* path)
621	{
622		BString absolutePath(test_path(path));
623		MonitoringInfo info(B_ENTRY_REMOVED, absolutePath);
624		BEntry entry;
625		FATAL_IF_ERROR(entry.SetTo(absolutePath),
626			"Failed to init BEntry for \"%s\"", absolutePath.String());
627		FATAL_IF_ERROR(entry.Remove(),
628			"Failed to remove entry \"%s\"", absolutePath.String());
629		return info;
630	}
631
632	MonitoringInfo TouchEntry(const char* path)
633	{
634		BString absolutePath(test_path(path));
635		FATAL_IF_POSIX_ERROR(utimes(absolutePath, NULL),
636			"Failed to touch \"%s\"", absolutePath.String());
637		MonitoringInfo info(B_STAT_CHANGED, absolutePath);
638		return info;
639	}
640
641	void ExpectNotification(const MonitoringInfo& info, bool expected = true)
642	{
643		if (!expected)
644			return;
645
646		AutoLocker<BLooper> locker(this);
647		if (fNotifications.IsEmpty()) {
648			locker.Unlock();
649			snooze(kMaxNotificationDelay);
650			locker.Lock();
651		}
652
653		if (fNotifications.IsEmpty()) {
654			FAIL("missing notification, expected:\n  %s",
655				info.ToString().String());
656		}
657
658		BMessage* message = fNotifications.RemoveItemAt(0);
659		ObjectDeleter<BMessage> messageDeleter(message);
660
661		if (!info.Matches(*message)) {
662			BString processedInfosString(_ProcessedInfosString());
663			FAIL("unexpected notification:\n  expected:\n  %s\n  got:\n%s%s",
664				info.ToString().String(),
665				indented_string(message_to_string(*message), "    ").String(),
666				processedInfosString.String());
667		}
668
669		fProcessedMonitoringInfos.Add(info);
670	}
671
672	void ExpectNotifications(MonitoringInfoSet infos)
673	{
674		bool waited = false;
675		AutoLocker<BLooper> locker(this);
676
677		while (!infos.IsEmpty()) {
678			if (fNotifications.IsEmpty()) {
679				locker.Unlock();
680				if (!waited) {
681					snooze(kMaxNotificationDelay);
682					waited = true;
683				}
684				locker.Lock();
685			}
686
687			if (fNotifications.IsEmpty()) {
688				FAIL("missing notification(s), expected:\n%s",
689					indented_string(infos.ToString(), "  ").String());
690			}
691
692			BMessage* message = fNotifications.RemoveItemAt(0);
693			ObjectDeleter<BMessage> messageDeleter(message);
694
695			bool foundMatch = false;
696			for (int32 i = 0; i < infos.CountInfos(); i++) {
697				const MonitoringInfo& info = infos.InfoAt(i);
698				if (info.Matches(*message)) {
699					infos.Remove(i);
700					foundMatch = true;
701					break;
702				}
703			}
704
705			if (foundMatch)
706				continue;
707
708			BString processedInfosString(_ProcessedInfosString());
709			FAIL("unexpected notification:\n  expected:\n%s\n  got:\n%s%s",
710				indented_string(infos.ToString(), "    ").String(),
711				indented_string(message_to_string(*message), "    ").String(),
712				processedInfosString.String());
713		}
714	}
715
716	virtual void DoInternal(bool directoriesOnly, bool filesOnly,
717		bool recursive, bool pathOnly, bool watchStat) = 0;
718
719private:
720	typedef BObjectList<BMessage> MessageList;
721	typedef BObjectList<MonitoringInfo> MonitoringInfoList;
722
723private:
724	virtual void MessageReceived(BMessage* message)
725	{
726		switch (message->what) {
727			case B_PATH_MONITOR:
728				if (!fNotifications.AddItem(new BMessage(*message)))
729					FATAL_IF_ERROR(B_NO_MEMORY, "Failed to store notification");
730				break;
731
732			default:
733				BLooper::MessageReceived(message);
734				break;
735		}
736	}
737
738private:
739	void _CreateDirectory(const char* path)
740	{
741		FATAL_IF_ERROR(create_directory(path, 0755),
742			"Failed to create directory \"%s\"", path);
743	}
744
745	void _RemoveRecursively(BEntry& entry)
746	{
747		// recurse, if the entry is a directory
748		if (entry.IsDirectory()) {
749			BDirectory directory;
750			FATAL_IF_ERROR(directory.SetTo(&entry),
751				"Failed to init BDirectory for \"%s\"",
752				BPath(&entry).Path());
753
754			BEntry childEntry;
755			while (directory.GetNextEntry(&childEntry) == B_OK)
756				_RemoveRecursively(childEntry);
757		}
758
759		// remove the entry
760		FATAL_IF_ERROR(entry.Remove(), "Failed to remove entry \"%s\"",
761			BPath(&entry).Path());
762	}
763
764	BString _ProcessedInfosString() const
765	{
766		BString processedInfosString;
767		if (!fProcessedMonitoringInfos.IsEmpty()) {
768			processedInfosString << "\nprocessed so far:\n"
769				<< indented_string(fProcessedMonitoringInfos.ToString(), "  ");
770		}
771		return processedInfosString;
772	}
773
774protected:
775	BString				fName;
776	uint32				fFlags;
777	thread_id			fLooperThread;
778	MessageList			fNotifications;
779	MonitoringInfoSet	fProcessedMonitoringInfos;
780	bool				fIsWatching;
781};
782
783
784struct TestBase : Test {
785protected:
786	TestBase(const char* name)
787		:
788		Test(name)
789	{
790	}
791
792	void StandardSetup()
793	{
794		CreateDirectory("base");
795		CreateDirectory("base/dir1");
796		CreateDirectory("base/dir1/dir0");
797		CreateFile("base/file0");
798		CreateFile("base/dir1/file0.0");
799	}
800};
801
802
803#define CREATE_TEST_WITH_CUSTOM_SETUP(name, code)						\
804	struct Test##name : TestBase {										\
805		Test##name() : TestBase(#name) {}								\
806		virtual void DoInternal(bool directoriesOnly, bool filesOnly,	\
807			bool recursive, bool pathOnly, bool watchStat)				\
808		{																\
809			code														\
810		}																\
811	};																	\
812	tests.push_back(new Test##name);
813
814#define CREATE_TEST(name, code)			\
815	CREATE_TEST_WITH_CUSTOM_SETUP(name, \
816		StandardSetup();				\
817		StartWatching("base");			\
818		code							\
819	)
820
821
822static void
823create_tests(std::vector<Test*>& tests)
824{
825	// test coverage:
826	// - file/directory outside
827	// - file/directory at top level
828	// - file/directory at sub level
829	// - move file/directory into/within/out of
830	// - move non-empty directory into/within/out of
831	// - create/move ancestor folder
832	// - remove/move ancestor folder
833	// - touch path, file/directory at top and sub level
834	// - base file instead of directory
835	//
836	// not covered (yet):
837	// - mount/unmount below/in our path
838	// - test symlink in watched path
839	// - attribute watching (should be similar to stat watching)
840
841	CREATE_TEST(FileOutside,
842		CreateFile("file1");
843		MoveEntry("file1", "file2");
844		RemoveEntry("file2");
845	)
846
847	CREATE_TEST(DirectoryOutside,
848		CreateDirectory("dir1");
849		MoveEntry("dir1", "dir2");
850		RemoveEntry("dir2");
851	)
852
853	CREATE_TEST(FileTopLevel,
854		ExpectNotification(CreateFile("base/file1"),
855			!directoriesOnly && !pathOnly);
856		ExpectNotification(MoveEntry("base/file1", "base/file2"),
857			!directoriesOnly && !pathOnly);
858		ExpectNotification(RemoveEntry("base/file2"),
859			!directoriesOnly && !pathOnly);
860	)
861
862	CREATE_TEST(DirectoryTopLevel,
863		ExpectNotification(CreateDirectory("base/dir2"),
864			!filesOnly && !pathOnly);
865		ExpectNotification(MoveEntry("base/dir2", "base/dir3"),
866			!filesOnly && !pathOnly);
867		ExpectNotification(RemoveEntry("base/dir3"),
868			!filesOnly && !pathOnly);
869	)
870
871	CREATE_TEST(FileSubLevel,
872		ExpectNotification(CreateFile("base/dir1/file1"),
873			recursive && !directoriesOnly);
874		ExpectNotification(MoveEntry("base/dir1/file1", "base/dir1/file2"),
875			recursive && !directoriesOnly);
876		ExpectNotification(RemoveEntry("base/dir1/file2"),
877			recursive && !directoriesOnly);
878	)
879
880	CREATE_TEST(DirectorySubLevel,
881		ExpectNotification(CreateDirectory("base/dir1/dir2"),
882			recursive && !filesOnly);
883		ExpectNotification(MoveEntry("base/dir1/dir2", "base/dir1/dir3"),
884			recursive && !filesOnly);
885		ExpectNotification(RemoveEntry("base/dir1/dir3"),
886			recursive && !filesOnly);
887	)
888
889	CREATE_TEST(FileMoveIntoTopLevel,
890		CreateFile("file1");
891		ExpectNotification(MoveEntry("file1", "base/file2"),
892			!directoriesOnly && !pathOnly);
893		ExpectNotification(RemoveEntry("base/file2"),
894			!directoriesOnly && !pathOnly);
895	)
896
897	CREATE_TEST(DirectoryMoveIntoTopLevel,
898		CreateDirectory("dir2");
899		ExpectNotification(MoveEntry("dir2", "base/dir3"),
900			!filesOnly && !pathOnly);
901		ExpectNotification(RemoveEntry("base/dir3"),
902			!filesOnly && !pathOnly);
903	)
904
905	CREATE_TEST(FileMoveIntoSubLevel,
906		CreateFile("file1");
907		ExpectNotification(MoveEntry("file1", "base/dir1/file2"),
908			recursive && !directoriesOnly);
909		ExpectNotification(RemoveEntry("base/dir1/file2"),
910			recursive && !directoriesOnly);
911	)
912
913	CREATE_TEST(DirectoryMoveIntoSubLevel,
914		CreateDirectory("dir2");
915		ExpectNotification(MoveEntry("dir2", "base/dir1/dir3"),
916			recursive && !filesOnly);
917		ExpectNotification(RemoveEntry("base/dir1/dir3"),
918			recursive && !filesOnly);
919	)
920
921	CREATE_TEST(FileMoveOutOfTopLevel,
922		ExpectNotification(CreateFile("base/file1"),
923			!directoriesOnly && !pathOnly);
924		ExpectNotification(MoveEntry("base/file1", "file2"),
925			!directoriesOnly && !pathOnly);
926		RemoveEntry("file2");
927	)
928
929	CREATE_TEST(DirectoryMoveOutOfTopLevel,
930		ExpectNotification(CreateDirectory("base/dir2"),
931			!filesOnly && !pathOnly);
932		ExpectNotification(MoveEntry("base/dir2", "dir3"),
933			!filesOnly && !pathOnly);
934		RemoveEntry("dir3");
935	)
936
937	CREATE_TEST(FileMoveOutOfSubLevel,
938		ExpectNotification(CreateFile("base/dir1/file1"),
939			recursive && !directoriesOnly);
940		ExpectNotification(MoveEntry("base/dir1/file1", "file2"),
941			recursive && !directoriesOnly);
942		RemoveEntry("file2");
943	)
944
945	CREATE_TEST(DirectoryMoveOutOfSubLevel,
946		ExpectNotification(CreateDirectory("base/dir1/dir2"),
947			recursive && !filesOnly);
948		ExpectNotification(MoveEntry("base/dir1/dir2", "dir3"),
949			recursive && !filesOnly);
950		RemoveEntry("dir3");
951	)
952
953	CREATE_TEST(FileMoveToTopLevel,
954		ExpectNotification(CreateFile("base/dir1/file1"),
955			!directoriesOnly && recursive);
956		ExpectNotification(MoveEntry("base/dir1/file1", "base/file2"),
957			!directoriesOnly && !pathOnly);
958		ExpectNotification(RemoveEntry("base/file2"),
959			!directoriesOnly && !pathOnly);
960	)
961
962	CREATE_TEST(DirectoryMoveToTopLevel,
963		ExpectNotification(CreateDirectory("base/dir1/dir2"),
964			!filesOnly && recursive);
965		ExpectNotification(MoveEntry("base/dir1/dir2", "base/dir3"),
966			!filesOnly && !pathOnly);
967		ExpectNotification(RemoveEntry("base/dir3"),
968			!filesOnly && !pathOnly);
969	)
970
971	CREATE_TEST(FileMoveToSubLevel,
972		ExpectNotification(CreateFile("base/file1"),
973			!directoriesOnly && !pathOnly);
974		ExpectNotification(MoveEntry("base/file1", "base/dir1/file2"),
975			!directoriesOnly && !pathOnly);
976		ExpectNotification(RemoveEntry("base/dir1/file2"),
977			!directoriesOnly && recursive);
978	)
979
980	CREATE_TEST(DirectoryMoveToSubLevel,
981		ExpectNotification(CreateDirectory("base/dir2"),
982			!filesOnly && !pathOnly);
983		ExpectNotification(MoveEntry("base/dir2", "base/dir1/dir3"),
984			!filesOnly && !pathOnly);
985		ExpectNotification(RemoveEntry("base/dir1/dir3"),
986			!filesOnly && recursive);
987	)
988
989	CREATE_TEST(NonEmptyDirectoryMoveIntoTopLevel,
990		CreateDirectory("dir2");
991		CreateDirectory("dir2/dir3");
992		CreateDirectory("dir2/dir4");
993		CreateFile("dir2/file1");
994		CreateFile("dir2/dir3/file2");
995		ExpectNotification(MoveEntry("dir2", "base/dir5"),
996			!filesOnly && !pathOnly);
997		if (recursive && filesOnly) {
998			ExpectNotifications(MonitoringInfoSet()
999				.Add(B_ENTRY_CREATED, "base/dir5/file1")
1000				.Add(B_ENTRY_CREATED, "base/dir5/dir3/file2"));
1001		}
1002	)
1003
1004	CREATE_TEST(NonEmptyDirectoryMoveIntoSubLevel,
1005		CreateDirectory("dir2");
1006		CreateDirectory("dir2/dir3");
1007		CreateDirectory("dir2/dir4");
1008		CreateFile("dir2/file1");
1009		CreateFile("dir2/dir3/file2");
1010		ExpectNotification(MoveEntry("dir2", "base/dir1/dir5"),
1011			!filesOnly && recursive);
1012		if (recursive && filesOnly) {
1013			ExpectNotifications(MonitoringInfoSet()
1014				.Add(B_ENTRY_CREATED, "base/dir1/dir5/file1")
1015				.Add(B_ENTRY_CREATED, "base/dir1/dir5/dir3/file2"));
1016		}
1017	)
1018
1019	CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveOutOfTopLevel,
1020		StandardSetup();
1021		CreateDirectory("base/dir2");
1022		CreateDirectory("base/dir2/dir3");
1023		CreateDirectory("base/dir2/dir4");
1024		CreateFile("base/dir2/file1");
1025		CreateFile("base/dir2/dir3/file2");
1026		StartWatching("base");
1027		MonitoringInfoSet filesRemoved;
1028		if (recursive && filesOnly) {
1029			filesRemoved
1030				.Add(B_ENTRY_REMOVED, "base/dir2/file1")
1031				.Add(B_ENTRY_REMOVED, "base/dir2/dir3/file2");
1032		}
1033		ExpectNotification(MoveEntry("base/dir2", "dir5"),
1034			!filesOnly && !pathOnly);
1035		ExpectNotifications(filesRemoved);
1036	)
1037
1038	CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveOutOfSubLevel,
1039		StandardSetup();
1040		CreateDirectory("base/dir1/dir2");
1041		CreateDirectory("base/dir1/dir2/dir3");
1042		CreateDirectory("base/dir1/dir2/dir4");
1043		CreateFile("base/dir1/dir2/file1");
1044		CreateFile("base/dir1/dir2/dir3/file2");
1045		StartWatching("base");
1046		MonitoringInfoSet filesRemoved;
1047		if (recursive && filesOnly) {
1048			filesRemoved
1049				.Add(B_ENTRY_REMOVED, "base/dir1/dir2/file1")
1050				.Add(B_ENTRY_REMOVED, "base/dir1/dir2/dir3/file2");
1051		}
1052		ExpectNotification(MoveEntry("base/dir1/dir2", "dir5"),
1053			!filesOnly && recursive);
1054		ExpectNotifications(filesRemoved);
1055	)
1056
1057	CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveToTopLevel,
1058		StandardSetup();
1059		CreateDirectory("base/dir1/dir2");
1060		CreateDirectory("base/dir1/dir2/dir3");
1061		CreateDirectory("base/dir1/dir2/dir4");
1062		CreateFile("base/dir1/dir2/file1");
1063		CreateFile("base/dir1/dir2/dir3/file2");
1064		StartWatching("base");
1065		MonitoringInfoSet filesMoved;
1066		if (recursive && filesOnly) {
1067			filesMoved
1068				.Add(B_ENTRY_REMOVED, "base/dir1/dir2/file1")
1069				.Add(B_ENTRY_REMOVED, "base/dir1/dir2/dir3/file2");
1070		}
1071		ExpectNotification(MoveEntry("base/dir1/dir2", "base/dir5"),
1072			!filesOnly && !pathOnly);
1073		if (recursive && filesOnly) {
1074			filesMoved
1075				.Add(B_ENTRY_CREATED, "base/dir5/file1")
1076				.Add(B_ENTRY_CREATED, "base/dir5/dir3/file2");
1077		}
1078		ExpectNotifications(filesMoved);
1079	)
1080
1081	CREATE_TEST_WITH_CUSTOM_SETUP(NonEmptyDirectoryMoveToSubLevel,
1082		StandardSetup();
1083		CreateDirectory("base/dir2");
1084		CreateDirectory("base/dir2/dir3");
1085		CreateDirectory("base/dir2/dir4");
1086		CreateFile("base/dir2/file1");
1087		CreateFile("base/dir2/dir3/file2");
1088		StartWatching("base");
1089		MonitoringInfoSet filesMoved;
1090		if (recursive && filesOnly) {
1091			filesMoved
1092				.Add(B_ENTRY_REMOVED, "base/dir2/file1")
1093				.Add(B_ENTRY_REMOVED, "base/dir2/dir3/file2");
1094		}
1095		ExpectNotification(MoveEntry("base/dir2", "base/dir1/dir5"),
1096			!filesOnly && !pathOnly);
1097		if (recursive && filesOnly) {
1098			filesMoved
1099				.Add(B_ENTRY_CREATED, "base/dir1/dir5/file1")
1100				.Add(B_ENTRY_CREATED, "base/dir1/dir5/dir3/file2");
1101		}
1102		ExpectNotifications(filesMoved);
1103	)
1104
1105	CREATE_TEST_WITH_CUSTOM_SETUP(CreateAncestor,
1106		StartWatching("ancestor/base");
1107		CreateDirectory("ancestor");
1108	)
1109
1110	CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestor,
1111		CreateDirectory("ancestorSibling");
1112		StartWatching("ancestor/base");
1113		MoveEntry("ancestorSibling", "ancestor");
1114	)
1115
1116	CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestorWithBase,
1117		CreateDirectory("ancestorSibling");
1118		CreateDirectory("ancestorSibling/base");
1119		StartWatching("ancestor/base");
1120		MoveEntry("ancestorSibling", "ancestor");
1121		MonitoringInfoSet entriesCreated;
1122		if (!filesOnly)
1123			entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base");
1124		ExpectNotifications(entriesCreated);
1125	)
1126
1127	CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestorWithBaseAndFile,
1128		CreateDirectory("ancestorSibling");
1129		CreateDirectory("ancestorSibling/base");
1130		CreateFile("ancestorSibling/base/file1");
1131		StartWatching("ancestor/base");
1132		MoveEntry("ancestorSibling", "ancestor");
1133		MonitoringInfoSet entriesCreated;
1134		if (!filesOnly)
1135			entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base");
1136		else if (!pathOnly)
1137			entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/file1");
1138		ExpectNotifications(entriesCreated);
1139	)
1140
1141	CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateAncestorWithBaseAndDirectory,
1142		CreateDirectory("ancestorSibling");
1143		CreateDirectory("ancestorSibling/base");
1144		CreateDirectory("ancestorSibling/base/dir1");
1145		CreateFile("ancestorSibling/base/dir1/file1");
1146		StartWatching("ancestor/base");
1147		MoveEntry("ancestorSibling", "ancestor");
1148		MonitoringInfoSet entriesCreated;
1149		if (!filesOnly) {
1150			entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base");
1151		} else if (recursive)
1152			entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/dir1/file1");
1153		ExpectNotifications(entriesCreated);
1154	)
1155
1156	CREATE_TEST_WITH_CUSTOM_SETUP(CreateBase,
1157		CreateDirectory("ancestor");
1158		StartWatching("ancestor/base");
1159		ExpectNotification(CreateDirectory("ancestor/base"),
1160			!filesOnly);
1161	)
1162
1163	CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateBase,
1164		CreateDirectory("ancestor");
1165		CreateDirectory("ancestor/baseSibling");
1166		StartWatching("ancestor/base");
1167		ExpectNotification(MoveEntry("ancestor/baseSibling", "ancestor/base"),
1168			!filesOnly);
1169	)
1170
1171	CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateBaseWithFile,
1172		CreateDirectory("ancestor");
1173		CreateDirectory("ancestor/baseSibling");
1174		CreateFile("ancestor/baseSibling/file1");
1175		StartWatching("ancestor/base");
1176		ExpectNotification(MoveEntry("ancestor/baseSibling", "ancestor/base"),
1177			!filesOnly);
1178		MonitoringInfoSet entriesCreated;
1179		if (filesOnly && !pathOnly)
1180			entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/file1");
1181		ExpectNotifications(entriesCreated);
1182	)
1183
1184	CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateBaseWithDirectory,
1185		CreateDirectory("ancestor");
1186		CreateDirectory("ancestor/baseSibling");
1187		CreateDirectory("ancestor/baseSibling/dir1");
1188		CreateFile("ancestor/baseSibling/dir1/file1");
1189		StartWatching("ancestor/base");
1190		ExpectNotification(MoveEntry("ancestor/baseSibling", "ancestor/base"),
1191			!filesOnly);
1192		MonitoringInfoSet entriesCreated;
1193		if (filesOnly && recursive)
1194			entriesCreated.Add(B_ENTRY_CREATED, "ancestor/base/dir1/file1");
1195		ExpectNotifications(entriesCreated);
1196	)
1197
1198	CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveAncestorWithBaseAndFile,
1199		CreateDirectory("ancestor");
1200		CreateDirectory("ancestor/base");
1201		CreateFile("ancestor/base/file1");
1202		StartWatching("ancestor/base");
1203		MonitoringInfoSet entriesRemoved;
1204		if (!filesOnly)
1205			entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base");
1206		else if (!pathOnly)
1207			entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/file1");
1208		MoveEntry("ancestor", "ancestorSibling");
1209		ExpectNotifications(entriesRemoved);
1210	)
1211
1212	CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveAncestorWithBaseAndDirectory,
1213		CreateDirectory("ancestor");
1214		CreateDirectory("ancestor/base");
1215		CreateDirectory("ancestor/base/dir1");
1216		CreateFile("ancestor/base/dir1/file1");
1217		StartWatching("ancestor/base");
1218		MonitoringInfoSet entriesRemoved;
1219		if (!filesOnly)
1220			entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base");
1221		else if (recursive)
1222			entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/dir1/file1");
1223		MoveEntry("ancestor", "ancestorSibling");
1224		ExpectNotifications(entriesRemoved);
1225	)
1226
1227	CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveBaseWithFile,
1228		CreateDirectory("ancestor");
1229		CreateDirectory("ancestor/base");
1230		CreateFile("ancestor/base/file1");
1231		StartWatching("ancestor/base");
1232		MonitoringInfoSet entriesRemoved;
1233		if (filesOnly && !pathOnly)
1234			entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/file1");
1235		ExpectNotification(MoveEntry("ancestor/base", "ancestor/baseSibling"),
1236			!filesOnly);
1237		ExpectNotifications(entriesRemoved);
1238	)
1239
1240	CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveBaseWithDirectory,
1241		CreateDirectory("ancestor");
1242		CreateDirectory("ancestor/base");
1243		CreateDirectory("ancestor/base/dir1");
1244		CreateFile("ancestor/base/dir1/file1");
1245		StartWatching("ancestor/base");
1246		MonitoringInfoSet entriesRemoved;
1247		if (filesOnly && recursive)
1248			entriesRemoved.Add(B_ENTRY_REMOVED, "ancestor/base/dir1/file1");
1249		ExpectNotification(MoveEntry("ancestor/base", "ancestor/baseSibling"),
1250			!filesOnly);
1251		ExpectNotifications(entriesRemoved);
1252	)
1253
1254	CREATE_TEST(TouchBase,
1255		ExpectNotification(TouchEntry("base"), watchStat && !filesOnly);
1256	)
1257
1258	CREATE_TEST(TouchFileTopLevel,
1259		ExpectNotification(TouchEntry("base/file0"),
1260			watchStat && recursive && !directoriesOnly);
1261	)
1262
1263	CREATE_TEST(TouchFileSubLevel,
1264		ExpectNotification(TouchEntry("base/dir1/file0.0"),
1265			watchStat && recursive && !directoriesOnly);
1266	)
1267
1268	CREATE_TEST(TouchDirectoryTopLevel,
1269		ExpectNotification(TouchEntry("base/dir1"),
1270			watchStat && recursive && !filesOnly);
1271	)
1272
1273	CREATE_TEST(TouchDirectorySubLevel,
1274		ExpectNotification(TouchEntry("base/dir1/dir0"),
1275			watchStat && recursive && !filesOnly);
1276	)
1277
1278	CREATE_TEST_WITH_CUSTOM_SETUP(CreateFileBase,
1279		StartWatching("file");
1280		ExpectNotification(CreateFile("file"),
1281			!directoriesOnly);
1282	)
1283
1284	CREATE_TEST_WITH_CUSTOM_SETUP(MoveCreateFileBase,
1285		CreateFile("fileSibling");
1286		StartWatching("file");
1287		ExpectNotification(MoveEntry("fileSibling", "file"),
1288			!directoriesOnly);
1289	)
1290
1291	CREATE_TEST_WITH_CUSTOM_SETUP(RemoveFileBase,
1292		CreateFile("file");
1293		StartWatching("file");
1294		ExpectNotification(RemoveEntry("file"),
1295			!directoriesOnly);
1296	)
1297
1298	CREATE_TEST_WITH_CUSTOM_SETUP(MoveRemoveFileBase,
1299		CreateFile("file");
1300		StartWatching("file");
1301		ExpectNotification(MoveEntry("file", "fileSibling"),
1302			!directoriesOnly);
1303	)
1304
1305	CREATE_TEST_WITH_CUSTOM_SETUP(TouchFileBase,
1306		CreateFile("file");
1307		StartWatching("file");
1308		ExpectNotification(TouchEntry("file"),
1309			watchStat && !directoriesOnly);
1310	)
1311}
1312
1313
1314static void
1315run_tests(std::set<BString> testNames, uint32 watchFlags,
1316	size_t& totalTests, size_t& succeededTests)
1317{
1318	std::vector<Test*> tests;
1319	create_tests(tests);
1320
1321	// filter the tests, if test names have been specified
1322	size_t testCount = tests.size();
1323	if (!testNames.empty()) {
1324		for (size_t i = 0; i < testCount;) {
1325			Test* test = tests[i];
1326			std::set<BString>::iterator it = testNames.find(test->Name());
1327			if (it != testNames.end()) {
1328				testNames.erase(it);
1329				i++;
1330			} else {
1331				tests.erase(tests.begin() + i);
1332				test->Delete();
1333				testCount--;
1334			}
1335		}
1336
1337		if (!testNames.empty()) {
1338			printf("no such test(s):\n");
1339			for (std::set<BString>::iterator it = testNames.begin();
1340				it != testNames.end(); ++it) {
1341				printf("  %s\n", it->String());
1342				exit(1);
1343			}
1344		}
1345	}
1346
1347	printf("\nrunning tests with flags: %s\n",
1348		watch_flags_to_string(watchFlags).String());
1349
1350	int32 longestTestName = 0;
1351
1352	for (size_t i = 0; i < testCount; i++) {
1353		Test* test = tests[i];
1354		longestTestName = std::max(longestTestName, test->Name().Length());
1355	}
1356
1357	for (size_t i = 0; i < testCount; i++) {
1358		Test* test = tests[i];
1359		bool terminate = false;
1360
1361		try {
1362			totalTests++;
1363			test->Init(watchFlags);
1364			printf("  %s: %*s", test->Name().String(),
1365				int(longestTestName - test->Name().Length()), "");
1366			fflush(stdout);
1367			test->Do();
1368			printf("SUCCEEDED\n");
1369			succeededTests++;
1370		} catch (FatalException& exception) {
1371			printf("FAILED FATALLY\n");
1372			printf("%s\n",
1373				indented_string(exception.Message(), "    ").String());
1374			terminate = true;
1375		} catch (TestException& exception) {
1376			printf("FAILED\n");
1377			printf("%s\n",
1378				indented_string(exception.Message(), "    ").String());
1379		}
1380
1381		test->Delete();
1382
1383		if (terminate)
1384			exit(1);
1385	}
1386}
1387
1388
1389int
1390main(int argc, const char* const* argv)
1391{
1392	// any args are test names
1393	std::set<BString> testNames;
1394	for (int i = 1; i < argc; i++)
1395		testNames.insert(argv[i]);
1396
1397	// flags that can be combined arbitrarily
1398	const uint32 kFlags[] = {
1399		B_WATCH_NAME,
1400		B_WATCH_STAT,
1401		// not that interesting, since similar to B_WATCH_STAT: B_WATCH_ATTR
1402		B_WATCH_DIRECTORY,
1403		B_WATCH_RECURSIVELY,
1404	};
1405	const size_t kFlagCount = sizeof(kFlags) / sizeof(kFlags[0]);
1406
1407	size_t totalTests = 0;
1408	size_t succeededTests = 0;
1409
1410	for (size_t i = 0; i < 1 << kFlagCount; i++) {
1411		// construct flags mask
1412		uint32 flags = 0;
1413		for (size_t k = 0; k < kFlagCount; k++) {
1414			if ((i & (1 << k)) != 0)
1415				flags |= kFlags[k];
1416		}
1417
1418		// run tests -- in recursive mode do that additionally for the mutually
1419		// B_WATCH_FILES_ONLY and B_WATCH_DIRECTORIES_ONLY flags.
1420		run_tests(testNames, flags, totalTests, succeededTests);
1421		if ((flags & B_WATCH_RECURSIVELY) != 0) {
1422			run_tests(testNames, flags | B_WATCH_FILES_ONLY, totalTests,
1423				succeededTests);
1424			run_tests(testNames, flags | B_WATCH_DIRECTORIES_ONLY, totalTests,
1425				succeededTests);
1426		}
1427	}
1428
1429	printf("\n");
1430	if (succeededTests == totalTests) {
1431		printf("ALL TESTS SUCCEEDED\n");
1432	} else {
1433		printf("%zu of %zu TESTS FAILED\n", totalTests - succeededTests,
1434			totalTests);
1435	}
1436
1437	return 0;
1438}
1439