/*
 * Copyright 2021-2024, ttcoder
 * All rights reserved. Distributed under the terms of the MIT license.
 */

/*
How to invoke this test harness:

	jam t1  # to test the first layer
	jam t2  # to test layers 1 to 2
	jam t3  # to test layers 1 to 3
	and so on

You may also selectively disable specific tests by tweaking BAIL_OUT_OR_PROCEED() below.
*/

// base.a
#include <base/log.h>
#include <base/env.h>


/******** "return" MACRO FLAGS ********/

// internals
#define FN_IS(name)	(0==strcmp(name, __func__))
#define BAIL_OUT	{ Genode::log("  .... skipping ", __func__, "() test ...."); return; }
#define PROCEED		{ Genode::log( "    ~ ~ ~ ~ ", __func__, " ~ ~ ~ ~ :" ); }

// actual configuration (comment/uncomment one of the four #defines as needed):
//
// run all tests
#define BAIL_OUT_OR_PROCEED  { PROCEED }
// run none of the tests (!)
//#define BAIL_OUT_OR_PROCEED  { BAIL_OUT }
// test all *except* these
//#define BAIL_OUT_OR_PROCEED  if( FN_IS("testSeven") || FN_IS("testMedia") ) { BAIL_OUT } else { PROCEED }
// test *only* these
//#define BAIL_OUT_OR_PROCEED  if( FN_IS("testBehemoth") || FN_IS("testkernel") ) { PROCEED } else { BAIL_OUT }


/******** #ifdef FLAGS ********/

// Unlike the above, there's no need to edit/define <hog_TEST_LEVEL> in source:
// This define gets passed to us automatically by jam:
//#define hog_TEST_LEVEL 1
	// Sensible values:
	// 0: no kits included
	// 1: SupportKit (first half)
	// 2: AppKit, and second half of SupportKit ?
	// 5: The BEHEMOTH, most of AppKit, much of InterfaceKit, most of StorageKit...

// An undocumented special case, is "test level 0", where we don't even need a
// libc/posix/a main() entry point, useful for debugging difficult 'bootstrapping' cases:
//#define hog_TEST_LEVEL 0

#ifndef hog_TEST_LEVEL
#	error "Misconfigured test -- hog_TEST_LEVEL macro not defined as it should"
#endif




#if hog_TEST_LEVEL >= 1

// libroot.so
#include <stdio.h>  // printf()

static void test_check( const char * line, bool passes )
{
	if( passes )
		Genode::log( " - Pass: ", line );  // don't use printf() here as that might produce "scrambled" logging, use a log() similar/symmetric to the below ones
	else
		// yellow tint (like PDBG() macro, except we don't add a heading __func__)
		Genode::log( "\033[33m", "** FAIL: ", line, "\033[0m " );
}

#define CHECK( condition )  test_check( #condition, (condition) );

#endif




//#pragma mark - t1

/***********************/
#if hog_TEST_LEVEL >= 1

// libbe.so
#include <OS.h>

status_t thread_waiting_on_thread( void * arg )
{
	thread_id * target_thread = (thread_id*) arg;
	CHECK( target_thread != NULL );
	
	CHECK( B_OK == wait_for_thread(*target_thread, NULL) );
	
	return B_OK;
}

static
status_t thread_to_be_joined( void * arg )
{
	CHECK( arg == NULL );
	
	//Genode::log( "  .........threaded routine starts snoozing..." );
	// allow wait_for_thread() to get going before we end in the normal case (but not in the test-zombie case)
	CHECK( B_OK == snooze(399000) );
///ToDo: had to increase the above as it seems a snooze(19000) lasts for *longer* (or gets started much later?) than the peer thread's snooze(999000) sometimes... how come ?
	// See e.g. the ordering in this trace:
	//	[init -> basic-layers]  - Pass: B_OK == snooze(999000)
	//	[init -> basic-layers]  - Pass: B_OK == snooze(19000)
	//Genode::log( "  .........threaded routine Done snoozing!" );
	
	return B_OK;
}

static
status_t thread_acquire_semaphore( void * arg )
{
	sem_id * sem_arg = (sem_id*)arg;
	
	CHECK( sem_arg != NULL );
	CHECK( B_OK == acquire_sem(*sem_arg) );
	
	return B_OK;
}


static
void testkernel()
{
	BAIL_OUT_OR_PROCEED;
	printf( "- Kernel Kit: thread tests -----------------\n" );
	
//+	CHECK( 0==strcmp(strerror(B_ERROR), "foo") );
///ToDo: <Unknown error: -1>
//+ printf("strerror (from BSD libc instead of ours?) yields: <%s>\n", strerror(B_ERROR));
//
	
	// check for "Error: libc suspend() called from non-user context" problem
	CHECK( B_OK == snooze(1000) );  // 1 ms
	
	// - check that we don't tickle Genode the wrong way (resume()+wait() must call Genode::Thread::start() only *once*)
	// - check for leaks in capabilities
	//
	
	//xx
	extern Genode::Env & HogEnv();
	//Genode::warning( "-- Leak-Check capabilities for threads ; T-:  ", HogEnv().pd().used_caps().value, ", ", HogEnv().pd().used_ram().value, " bytes" );
	
	// ---- Threads ----
	
///ToDo: also check for RAM leaks:
//	const unsigned base_mem_leakcheck = HogEnv().pd().used_ram().value; (etc)
	const size_t base_caps_leakcheck = HogEnv().pd().used_caps().value;
	
	// basics
	//
	
	thread_id both_resume_and_wait = spawn_thread( thread_to_be_joined, "will-be-joined", 1, NULL );
	CHECK( both_resume_and_wait > 0 );
	CHECK( both_resume_and_wait == find_thread("will-be-joined") );
	
	CHECK( B_OK == resume_thread(both_resume_and_wait) );
	CHECK( HogEnv().pd().used_caps().value == base_caps_leakcheck +1 );  // allow for exactly one consumed cap
	
	CHECK( B_OK == wait_for_thread(both_resume_and_wait, NULL) );
	CHECK( HogEnv().pd().used_caps().value == base_caps_leakcheck +0 );  // make sure the cap was returned
	// check that we don't crash with a *second* join():
	CHECK( B_BAD_THREAD_ID == wait_for_thread(both_resume_and_wait, NULL) );
	// epilogue
	CHECK( HogEnv().pd().used_caps().value == base_caps_leakcheck +0 );
	
	// check for *multiple* concurrent wait_for_thread()s all waiting on the same thread -- i.e. create one "rabbit" and four "foxes"
	// (this ensures robustness of <menu_tracking> and <w>menubar cached menu> threads in BAlert, BMenuBar et al)
	//
	
	thread_id shortlived = spawn_thread( thread_to_be_joined, "sleeper-with-multiple-joiners", 1, NULL );
	thread_id waiter1 = spawn_thread( thread_waiting_on_thread, "wait-on-main-1", 1, &shortlived );
	thread_id waiter2 = spawn_thread( thread_waiting_on_thread, "wait-on-main-2", 1, &shortlived );
#if 0
	thread_id waiter3 = spawn_thread( thread_waiting_on_thread, "wait-on-main-3", 1, &shortlived );
	thread_id waiter4 = spawn_thread( thread_waiting_on_thread, "wait-on-main-4", 1, &shortlived );
#endif
	CHECK( shortlived > 0 );
	CHECK( waiter1 > 0 );
	CHECK( waiter2 > 0 );
#if 0
	CHECK( waiter3 > 0 );
	CHECK( waiter4 > 0 );
#endif
	CHECK( B_OK == resume_thread(shortlived) );
	CHECK( B_OK == resume_thread(waiter1) );
	CHECK( B_OK == resume_thread(waiter2) );
#if 0
	CHECK( B_OK == resume_thread(waiter3) );
	CHECK( B_OK == resume_thread(waiter4) );
#endif
	
	CHECK( B_OK == snooze(999000) );
	// post-snooze: we should get multiple "OK", give them more time to display:
	CHECK( B_OK == wait_for_thread(waiter1, NULL) );
	CHECK( B_OK == wait_for_thread(waiter2, NULL) );
///ToDo: works with 2 waiters, but unable to make 3+ waiters work yet, probably need release_sem_etc( 2+ at a time ) feature to make it work... (or just call release_sem() as long as sem.cnt() > 0 ?)... Though Haiku code only ever has 2 waiters at most, so not an urgently needed fix ?
#if 0
	CHECK( B_OK == wait_for_thread(waiter3, NULL) );
	CHECK( B_OK == wait_for_thread(waiter4, NULL) );
#endif
	// epilogue
	CHECK( HogEnv().pd().used_caps().value == base_caps_leakcheck +0 );
	
	// check that wait_for_thread() works even on a thread that was NOT resume()d
	//
	
	thread_id just_wait = spawn_thread( thread_to_be_joined, "will-be-joined-2", 1, NULL );
	CHECK( just_wait > 0 );
	
	// do not call resume_thread(), call wait_for_thread() directly and let it handle resume'ing:
	CHECK( B_OK == wait_for_thread(just_wait, NULL) );
	
	// check joining a thread *after* it has terminated (i.e. "zombie"(?) thread)
	//
	
	thread_id zombie = spawn_thread( thread_to_be_joined, "will-be-joined-3", 1, NULL );
	CHECK( zombie > 0 );
	
	// make sure the thread terminates/zombifies : start it explicitely and let it go, so that wait_for_thread() does not have to start it on its own (we want it done _before_ reaching wait_for_thread())
	CHECK( B_OK == resume_thread(zombie) );
	CHECK( B_OK == snooze(999000) );
	
	// join: even if the thread has terminated on its own, its 'relic' (zombie) should be waiting for us to free its resources, via join/wait-for-thread:
	CHECK( B_OK == wait_for_thread(zombie, NULL) );  // and yes that means that join()ing is mandatory, otherwise resources are leaked...
	// check that we don't crash with a *second* join():
	CHECK( B_BAD_THREAD_ID == wait_for_thread(zombie, NULL) );
	
	// epilogue
	CHECK( HogEnv().pd().used_caps().value == base_caps_leakcheck +0 );
	
	// check for leaks when we fail to "join" a thread
	//
	
	thread_id not_joined = spawn_thread( thread_to_be_joined, "will-NOT-be-joined-4", 1, NULL );
	CHECK( not_joined > 0 );
	// make (extra) sure the thread terminates/zombifies:
	CHECK( B_OK == resume_thread(not_joined) );
	CHECK( B_OK == snooze(999000) );
///ToDo: why do we need this second snooze() for the thread to get executed (and finish) ?
//UPD: let's try to rem it out again:
//	CHECK( B_OK == snooze(1999000) );
	// do NOT call wait_for_thread(), make sure we're leak free in spite of that:
/////We sometime *fail* that test, if the thread-func has its snooze() intact
//	CHECK( HogEnv().pd().used_caps().value == base_caps_leakcheck +0 );  // libroot should have called join() implicitely, free'ing thread cap/mem ...
	//uh no it shouldn't, see below:
	
	// do a 'dummy' wait, to trigger kernel.doHouseKeeping()
	CHECK( B_BAD_THREAD_ID == wait_for_thread(999, NULL) );
	// it _is_ expected that we leak at this point, at least for pthreads ; do BeOS/Haiku threads have different semantics, i.e. they're supposed to clean themselves up ?
	CHECK( HogEnv().pd().used_caps().value == base_caps_leakcheck +1 );
	
	// now cancel the leak
	CHECK( B_OK == wait_for_thread(not_joined, NULL) );
	
	// epilogue
	CHECK( HogEnv().pd().used_caps().value == base_caps_leakcheck +0 );

///ToDo: add test for exit_thread(), used in BLooper::Quit(), esp. since its current implem seems weak	

	
	// ---- Semaphores ----
	
	printf( "- Kernel Kit: semaphore tests -----------------\n" );
	
	auto semaphore = create_sem( 0, "semaphore_testing" );
	
	// check that release_sem() releases the thread's acquire_sem() (even for *multiple* waiters)
	//
	
	thread_id sem_will_be_released_1 = spawn_thread( thread_acquire_semaphore, "wait_for_sem_release-1", 1, &semaphore );
	thread_id sem_will_be_released_2 = spawn_thread( thread_acquire_semaphore, "wait_for_sem_release-2", 1, &semaphore );
	CHECK( sem_will_be_released_1 > 0 );
	CHECK( sem_will_be_released_2 > 0 );
	CHECK( B_OK == resume_thread(sem_will_be_released_1) );
	CHECK( B_OK == resume_thread(sem_will_be_released_2) );
	CHECK( B_OK == snooze(999000) );
	
	// T0 -- (call release_sem() twice since release_sem_etc(semaphore, 2, 0) is n/i)
	CHECK( B_OK == release_sem(semaphore) );
	CHECK( B_OK == release_sem(semaphore) );
	
	CHECK( B_OK == wait_for_thread(sem_will_be_released_1, NULL) );
	CHECK( B_OK == wait_for_thread(sem_will_be_released_2, NULL) );
	
	// check that delete_sem() (!) releases, too
	//
	
	thread_id
		sem_will_be_Deleted = spawn_thread( thread_acquire_semaphore, "wait_for_sem_Deletion", 1, &semaphore );
	CHECK( sem_will_be_Deleted > 0 );
	CHECK( B_OK == resume_thread(sem_will_be_Deleted) );
	CHECK( B_OK == snooze(999000) );
	
	// T0 (!)
	CHECK( B_OK == delete_sem(semaphore) );
//_d_ CHECK( B_OK == snooze(999000) );
///ToDo: in ~10% of runs, this one fails to return B_OK..... (seems oddly sensitive to changes in the "snooze(1999..." above ?!)
//UPD: better with Thread overhaul?
	// this one now works; make *sure* to keep it working, without locking up:
	// (this ensures robustness of BAlert -- delete_sem() failing to release/up() the semaphore would break BAlert and others)
	CHECK( B_OK == wait_for_thread(sem_will_be_Deleted, NULL) );
	
	// misc checks
	//
	CHECK( B_BAD_SEM_ID == acquire_sem(semaphore) );
	CHECK( B_BAD_SEM_ID == release_sem(semaphore) );
	CHECK( B_BAD_SEM_ID == delete_sem(semaphore) );
	
	
	// ---- EPILOGUE (leak check) ----
	
	CHECK( HogEnv().pd().used_caps().value == base_caps_leakcheck +0 );
}

#if 0
static
void* thread_pipe_server( void* )
{
	// Rx from "client"
	//
	int rx = open( "/dev/upstream", O_RDONLY );
	CHECK( rx >= 0 );
	for( ; ; )
	{
		char buf[256] = {};
		const int got = read( rx, buf, sizeof(buf) );
		
		if( got == 0 )
			break;
		if( got < 0 )
		{
			Genode::error( "rx error: ", got );
			break;
		}
		
		Genode::log( "server rx:", (const char*) buf );/**/
	}
	CHECK( close(rx) == 0 );
	
	// Tx to "client"
	//
	int tx = open( "/dev/downstream", O_WRONLY );
	CHECK( tx >= 0 );
	CHECK( 3 == write(tx, "abc", 3) );
	CHECK( close(tx) == 0 );
	
//?	pthread_exit(NULL);
	return NULL;
}

static
void testStoragePipes()
{
	BAIL_OUT_OR_PROCEED;
	
	/*
	int in = 0;
	CHECK( pipe(&in) == 0 );
	CHECK( in >= 1 );
	*/
	
	// --- "server" ---
	
	Libc::with_libc([] () {
		pthread_t server = 0;
		CHECK( 0 ==  pthread_create(&server, nullptr, thread_pipe_server, nullptr) );
	});
	
	// --- "client" ---
	
	// Tx to "server"
	//
	int tx = open( "/dev/upstream", O_WRONLY );
	CHECK( tx >= 0 );
	CHECK( 3 == write(tx, "123", 3) );
	CHECK( close(tx) == 0 );
	
	// Rx from "server"
	int rx = open( "/dev/downstream", O_RDONLY );
	CHECK( rx >= 0 );
	for( ; ; )
	{
		char buf[256] = {};
		const int got = read( rx, buf, sizeof(buf) );
		
		if( got == 0 )
			break;
		if( got < 0 )
		{
			Genode::error( "rx error: ", got );
			break;
		}
		
		Genode::log( "client rx:", (const char*) buf );/**/
	}
	CHECK( close(rx) == 0 );
}
#endif


// libbe.so
#include <support/DataIO.h>
#include <support/List.h>
#include <support/Locker.h>
#include <support/Referenceable.h>
#include <support/String.h>
#include <support/StringList.h>

static
void testSupportKit()
{
	BAIL_OUT_OR_PROCEED;
	printf( "- Support Kit tests -----------------\n" );
	
	
	// BDataIO
	//
	
	{
		const BString data = "123456";
		
		BMallocIO mallocio;
		CHECK( mallocio.Position()==0 );
		
		mallocio.Write( data.String(), data.Length() );
		CHECK( mallocio.Position()==data.Length() );
	}
	
	
	// BList
	//
	
	BList list;
	CHECK( list.CountItems() == 0 );
	
	
	// BLocker
	//
	
	{
		BLocker locker;
		CHECK( locker.InitCheck() == B_OK );
		CHECK( locker.CountLocks() == 0 );
		
		CHECK( locker.Lock() == true );
		CHECK( locker.CountLocks() == 1 );
		
		// basic testing of _recursive_ locking:
		///TODO: create a second thread and test more elaborate scenarios?
		CHECK( locker.Lock() == true );
		CHECK( locker.CountLocks() == 2 );
		
		// T+ match two Lock()s with two Unlock()s but not more
		// (otherwise, any extra call will echo this warning to stderr: "Unlocking BLocker with sem 0 from wrong thread 1, current holder -1 (see issue #6400).")
		locker.Unlock();
		CHECK( locker.IsLocked() == true );
		locker.Unlock();
		CHECK( locker.IsLocked() == false );
	}
	
	
	///++ BObjectList
	
	
	// BReferenceable
	//
	
	BReferenceable just_check_for_linking_resolution_is_all;
	
	
	// BString
	//
	
	BString s1 = "OneTwo";
	
	BString s2 = "One";
	CHECK( false == (s1==s2) );
	
	s2 += "Two";
	CHECK( s1==s2 );
	
	
	// BStringList
	//
	
	BStringList stringlist;
	CHECK( stringlist.CountStrings() == 0 );
	stringlist.Add( "Test" );
	CHECK( stringlist.CountStrings() == 1 );
}

#endif


//#pragma mark - t2

/***********************/
#if hog_TEST_LEVEL >= 2

static
void testTranslationKit()
{
	BAIL_OUT_OR_PROCEED;
	printf( "- Translation Kit tests -----------------\n" );
	
	///+++ testTranslationKit
}

#endif


//#pragma mark - t3 (+ t7 !) -

/***********************/
#if hog_TEST_LEVEL >= 4

// libbe.so
#include <fs_attr.h>  // struct attr_info
#include <storage/Directory.h>
#include <storage/Entry.h>
#include <storage/File.h>
#include <storage/FindDirectory.h>
#include <storage/Path.h>
#include <storage/SymLink.h>
#include <storage/VolumeRoster.h>


//xx rename to "AssertDirContents"
static
void DirCheck( const char * prefix, BString str_list )
{
	BStringList list;
	CHECK( true == str_list.Split(",", true, list) );
	
///UPD: gone after 23.05? test in "client-server mode" -- FIXME: in the SECOND call to DirCheck (once mkdir was done), this opendir() *hangs* in the remote-vfs scenario (prefix == "/boot")
	DIR * dir_libc = opendir( prefix );
	
#if 1
	CHECK( dir_libc );
	for( int i = 0; i < list.CountStrings(); i++ )
	{
		dirent * entry = readdir( dir_libc );
		
		CHECK( entry );
	//	Genode::log( "  next, we should obtain entry <", list.StringAt(i).String(), "> vs <", (const char*)entry->d_name, ">" );
		CHECK( list.StringAt(i) == entry->d_name );
	}
	CHECK( NULL == readdir(dir_libc) );
#endif
	
	closedir( dir_libc );
}

static
void testStorage_with_libc( const char * prefix )
{
	BAIL_OUT_OR_PROCEED;
	
	// Check for correctness for <prefix> FS, i.e. in 3 FS "layers" (plain, attribute-able, index-able FS)
	// -> we're called 3 times, with 3 differents file systems
	//
	BString subdir; subdir << prefix << "/" << "grinding_tests_3_way_fs";
	printf( "- Storage Kit '3-way' tests : prefix <%s> and subdir <%s> -----------------\n", prefix, subdir.String() );
	
	// -- prologue --
	
	struct stat stats;
	
	// special-case: raw access to the NTFS partition exposes the 'hidden' haiku folder, so even when 'empty' it's actually not empty
	BString empty_dir_list;
	BString populated_dir_list;
	populated_dir_list << "grinding_tests_3_way_fs";
	;
	if( 0==strcmp(prefix, "/DataNtfs") )
	{
		empty_dir_list = "_Hai_Genode_Root_Folder_";
		populated_dir_list << "," << "_Hai_Genode_Root_Folder_";
	}
	
	// -- Sanity Check : do we have the pre-requisite FS ? --
	
	// this does not really test anything/tell us anything because if the parent <dir> tag exists then this succeeds...
	//CHECK( 0 == stat(prefix /* ! */, &stats) );
	
	// -- mkdir --
	
	// T- : subdir should not exist
#if 1
	CHECK( -1 == stat(subdir, &stats) );
	DirCheck( prefix, empty_dir_list );
#endif
	
	// T0 : mkdir
	CHECK(  0 == mkdir(subdir, 0777) );
#if 1
	CHECK( -1 == mkdir(subdir, 0777) );
	
	// T+ : subdir should exist
	CHECK( 0 == stat(subdir, &stats) );
#endif
	DirCheck( prefix, populated_dir_list );
	
	// -- rmdir --
	
	// T0 : rmdir
	CHECK(  0 == rmdir(subdir) );
#if 1
	CHECK( -1 == rmdir(subdir) );
	
	// T+ : subdir should no longer exist
	CHECK( -1 == stat(subdir, &stats) );
	DirCheck( prefix, empty_dir_list );
#endif
}


#if hog_TEST_LEVEL >= 7
#	include <storage/Query.h>
static
void setup_query_predicate( const char * predicate, BQuery & query, BVolume & vol )
{
	printf( " ---- testing query: <%s> ----\n", predicate );
	
	CHECK( B_OK == query.Clear() );
	CHECK( B_OK == query.SetVolume(&vol) );
	CHECK( B_OK == query.SetPredicate(predicate) );
	CHECK( B_OK == query.Fetch() );
}
#endif


/// FCNTL(): we get hammered by "Error: no plugin found for fcntl(10006)"
// But might be just os/src/lib/vfs/ram_file_system.h which does not implement fcntl(),
// and we'll be out of the woods when running tests on NTFS or BFS ?
// More likely, it's due to fs.cpp using "local" FDs (10001, 10002 etc),
// and libc_fd_to_fd() fails to map them as "Genode" FDs.


static
void testStorageMMD()
{
	BAIL_OUT_OR_PROCEED
	// == Emulate MMD's StationFolderSymlink class
	
	// We create it first, since the rest of tests below assumes /boot/Station exists.
	// (including the "test existence of symlink" test: with fs.cpp the test can only pass if the linked target exists -- this is a shortcoming of fs.cpp, compared to the "real" Haiku VFS which of course can distinguish a link from its linked target)
	CHECK( B_OK == create_directory("/boot/Station", 0777) );
	
	// Ctor + Update()
	//
	BPath settingsFolder;
	CHECK( B_OK == find_directory(B_USER_SETTINGS_DIRECTORY, &settingsFolder) );
	CHECK( B_OK == settingsFolder.Append("TuneTrackerSystems") );
	CHECK( BString("/boot/home/config/settings/TuneTrackerSystems") == settingsFolder.Path() );
	
	// ensure we are in an (existing) TTS settings folder
	CHECK( false == BEntry("/boot/home/config/settings/TuneTrackerSystems").Exists() );
	CHECK( B_OK == create_directory(settingsFolder.Path(), 0777) );
	
	// if it did not exist yet, create symlink with default target
	// == rawSet( "/boot/Station" );
	BDirectory parent( settingsFolder.Path() );
	CHECK( false == parent.Contains(".SymlinkToStationFolder", B_SYMLINK_NODE) );
	
	CHECK( true == BEntry("/boot/home/config/settings/TuneTrackerSystems").Exists() );
///ToDo: this test (and another one below) tend to fail, depending on vfs_indexer.lib.so mechanics...
	// maybe vfs's directory() predicate returns true unconditionnally on a "dot file" ?
	// maybe for the same reason I have a "access(/boot/.Music)" test above which I need to succeed ?)
	CHECK( false == BEntry("/boot/home/config/settings/TuneTrackerSystems/.SymlinkToStationFolder").Exists() );
#if 1
	DIR *dir_libc = NULL;
	
	// The output for /DataNtfs should NOT be identical to that of /boot : /boot is not just
	// an indexed layer on top, it is also chroot'ed to within a *subfolder* of /DataNtfs
	
	printf("\n--/DataNtfs------------------------------------------\n" );
	dir_libc = opendir("/DataNtfs");
	while (dirent *entry = readdir(dir_libc)) {
		printf("     << %s >> \n", entry->d_name);
	}
	closedir(dir_libc);
	;
	printf("\n--/boot------------------------------------------\n" );
	dir_libc = opendir("/boot");
	while (dirent *entry = readdir(dir_libc)) {
		printf("     << %s >> \n", entry->d_name);
	}
	closedir(dir_libc);
	
	printf("\n--/boot/home------------------------------------------\n" );
	dir_libc = opendir("/boot/home");
	while (dirent *entry = readdir(dir_libc)) {
		printf("     << %s >> \n", entry->d_name);
	}
	closedir(dir_libc);
#endif
	
	BNode statable;//
	CHECK( B_ENTRY_NOT_FOUND == statable.SetTo  ("/boot/home/config/settings/TuneTrackerSystems/.SymlinkToStationFolder") );
	
	// Symlinks
	// - libc:
	CHECK( -1 == symlink( "/", "/foobar-non-existent-directory/symlink-to-slash" ) );
	CHECK( 0 == symlink( "/", "/boot/symlink-to-slash" ) );
	char buf_readlink[64] = {};
	CHECK( strlen("/") == readlink( "/boot/symlink-to-slash", buf_readlink, sizeof(buf_readlink) ) );
	CHECK( 0 == strcmp(buf_readlink, "/") );
	CHECK( 0 == symlink( "/", "/boot/home/config/symlink-to-slash" ) );
	CHECK( 0 == symlink( "/boot", "/boot/home/config/.SymlinkToStationFolder" ) );
	CHECK( 0 == symlink( "/boot/Station", "/boot/home/config/settings/.SymlinkToStationFolder" ) );
	// - libbe:
	BSymLink dummy;
	CHECK( B_OK == parent.CreateSymLink(
			".SymlinkToStationFolder",
			"/boot/Station",
			&dummy
			)
		);
	CHECK( true == BEntry("/boot/home/config/settings/TuneTrackerSystems/.SymlinkToStationFolder").Exists() );
	CHECK( B_OK == statable.SetTo("/boot/home/config/settings/TuneTrackerSystems/.SymlinkToStationFolder") );
	CHECK( B_OK == statable.SetTo("/boot/Station") );
	
	///we don't pass this test, probably due to shortcoming of fs.cpp (emulated Haiku VFS) which cannot stat() the symlink itself, but only its target) ;
	// maybe this test would ""pass"" with B_Directory_NODE instead of B_SYMLINK_NODE ? Hmm nope... I'm in for some deep debugging of fs.cpp in the future...
//UPD we pass the test with NTFS "accidentally", as of 14dec2022, because the link's target  now exists, due to modified test structure...
///ToDo: Setup a test on a different symlink, whose target does not exist, then this will fail to pass again, exposing the problem again
//	CHECK( true == parent.Contains(".SymlinkToStationFolder", B_SYMLINK_NODE) );
	
	// CreateStationFolderIfNeeded()
	//
	BPath target;  // station folder
	// == GetTargetPath( target );
	// read symlink's contents
	BDirectory dir( settingsFolder.Path() );
	BSymLink retrieve( &dir, ".SymlinkToStationFolder" );
	CHECK( true == retrieve.IsAbsolute() );
	CHECK( strlen("/boot/Station") == retrieve.MakeLinkedPath(
			settingsFolder.Path(),
			&target
			)
		);
	
	// oddly, despite the above failure to return B_OK, we do get the correct value:
	CHECK( BString("/boot/Station") == target.Path() );
	
	//_d_ we now create /boot/Station up there, not down here:
	//CHECK( false == BEntry(target.Path()).Exists() );
	CHECK( B_OK == create_directory(target.Path(), 0777) );
	CHECK( true == BEntry(target.Path()).Exists() );
	
	// == PaneBase.cpp
	
	//..
}


static
void testStorageKit_ram()
{
	BAIL_OUT_OR_PROCEED
	
	// *** required: ***
	// a <config> with read/writeable "/ram_rw" VFS is required for the below:
	
	
	printf( "- libc (FreeBSD) tests -----------------\n" );
	
	struct stat dummy_stat;
	CHECK( -1 == stat ("/ram_rw/LibC_mkdir", &dummy_stat) );
	CHECK(  0 == mkdir("/ram_rw/LibC_mkdir", 0777) );
	CHECK(  0 == stat ("/ram_rw/LibC_mkdir", &dummy_stat) );
	
	
	printf( "- Storage Kit tests : RAM -----------------\n" );
	
	// BStatable:
	BNode statable;
	CHECK( B_ENTRY_NOT_FOUND == statable.SetTo  ("/ram_rw/Haiku_createdir") );
	CHECK( B_OK == create_directory("/ram_rw/Haiku_createdir", 0777) );
	CHECK( B_OK == statable.SetTo  ("/ram_rw/Haiku_createdir") );
	
	// then test the whole directory structure, in one fell swoop:
	//...note: there is also a create_path() in find_directory.cpp, which claims to do the whole dir structure, how does create_path() differ from create_directory() ?
	CHECK( B_ENTRY_NOT_FOUND == statable.SetTo("/ram_rw/home/config/settings/TuneTrackerSystems/.SymlinkToStationFolder") );
	CHECK( B_OK == create_directory("/ram_rw/home/config/settings/TuneTrackerSystems", 0777) );
	CHECK( B_OK == statable.SetTo(  "/ram_rw/home/config/settings/TuneTrackerSystems") );
	
	BPath path;
	CHECK( path.InitCheck() == B_NO_INIT );
	
	// BDirectory
	BDirectory dir( "/" );
	CHECK( dir.InitCheck() == B_OK );
	
	// BEntry - get_ref_for_path()
	entry_ref be_ref;
	CHECK( B_OK == get_ref_for_path("/beos.mp3", &be_ref) );
	CHECK( be_ref.name && !strcmp(be_ref.name, "beos.mp3") );
	
	// BEntry
	BEntry entry;
	//dir.GetEntry( &entry );
	//CHECK OK
	//CHECK is "/"
	
	// BEntry / BDirectory
	CHECK( B_OK == dir.SetTo("/ram_rw") );
	CHECK( B_OK == dir.GetNextEntry(&entry, false) );  // traverse: false
	CHECK( BPath(&entry).Path() == BString("/ram_rw/LibC_mkdir") );
	CHECK( B_OK == dir.GetNextEntry(&entry, false) );
	CHECK( BPath(&entry).Path() == BString("/ram_rw/Haiku_createdir") );
	CHECK( B_OK == dir.GetNextEntry(&entry, false) );
	CHECK( BPath(&entry).Path() == BString("/ram_rw/home") );
	CHECK( B_ENTRY_NOT_FOUND == dir.GetNextEntry(&entry, false) );
	
	// find_directory, BPath
	BPath path_;
	CHECK( B_OK == find_directory(B_SYSTEM_DATA_DIRECTORY, &path_) );  // needed by e.g. TTracker::InstallTemporaryBackgroundImages()
	CHECK( BString("/system/data") == path_.Path() );
}

/********/
extern "C" int _kern_create_dir(int, const char*, int);

static
void testStorageKit_ntfs_MemoryLeak()
{
	BAIL_OUT_OR_PROCEED
	
	printf( "- Storage Kit tests : NTFS (leaks) -----------------\n" );
	
	// Or ---> /DataNtfs/_Hai_Genode_Root_Folder_
	CHECK( B_OK == create_directory("/boot/Music", 0777) );
	
	{
		BNode node_w_attrs;
		const BString val( "some_artist" );
		
		// Or ---> /DataNtfs/_Hai_Genode_Root_Folder_
		CHECK( B_OK == node_w_attrs.SetTo("/boot/Music") );  // xattr's work even on directories (at least with NTFS)
		CHECK( B_OK == node_w_attrs.WriteAttrString("Audio:Artist", &val) );
	}
/*
	int fd = open( "/boot/Music", O_CREAT | O_WRONLY );
	CHECK( fd > 0 );
	ssize_t result = fs_write_attr(fd, "Audio:Artist", B_STRING_TYPE, 0, "1234", 4);
	CHECK( result == 4 );
	CHECK( 0 == close(fd) );
*/
/*
	BFile f( "/boot/Text", B_CREATE_FILE|B_WRITE_ONLY );
	CHECK( B_OK == f.InitCheck() );
	CHECK( 4 == f.Write("1234", 4) );
*/
	struct stat st = {};
	CHECK( -1 == stat("/DataNtfs/FooBar...to-trigger-Leak-Checkpoint", &st) );  // to trigger a LeakChecker.Dump()...
	
	// Verified leak (8mar23): each of these leaks 1000 bytes:
	CHECK( B_OK == _kern_create_dir(-1, "/DataNtfs/_Hai_Genode_Root_Folder_/sub_1", 0777) );
//	CHECK( B_OK == _kern_create_dir(-1, "/DataNtfs/_Hai_Genode_Root_Folder_/sub_2", 0777) );
/*
	CHECK( B_OK == create_directory( "/DataNtfs/_Hai_Genode_Root_Folder_/sub_1", 0777) );
	CHECK( B_OK == create_directory( "/DataNtfs/_Hai_Genode_Root_Folder_/sub_2", 0777) );
	CHECK( B_OK == create_directory( "/DataNtfs/_Hai_Genode_Root_Folder_/sub_3", 0777) );
*/
	
	//CHECK( -1 == stat("/DataNtfs/FooBar...to-trigger-Leak-Checkpoint", &st) );  // to trigger a LeakChecker.Dump()...
}


static
void testStorageKit_ntfs()
{
	BAIL_OUT_OR_PROCEED
	
	int dummy_int = 0;
	
	// *** required: ***
	// a <config> with read/writeable *NTFS* (or other xattr-supporting FS) node is required for the below:
	
	printf( "- Storage Kit tests : NTFS -----------------\n" );
	
#if 1
	{
		struct stat st = {};
		CHECK(  0==stat("/DataNtfs", &st) );
			// libNTFS is not involved in that "root/top" stat
		CHECK( -1==stat("/DataNtfs/FUBAR", &st) );
			// that first call leaks (or caches?) 1168 bytes from libNTFS
		CHECK( -1==stat("/DataNtfs/FUBAR", &st) );
			// no additional leaking on that one
		
		// test for bugs that occur when NTFS and Indexer are on opposite sides of the client/server continuum
		CHECK( -1==stat("/boot/FUBAR", &st) );
////UPD: gone with 23.05 ? FIXME: new test scheme with vfs-server: now we get stuck on this (even though the above pass)... so NTFS direct, works, but INDEXER, indirect, gets stuck ?
		CHECK( false == BEntry("/boot/Fubar").Exists() );  // check for "chroot" -- the "secret" files should not be visible (ought to be mounted *above* the chroot folder)
	}
	
	//xx
	extern Genode::Env & HogEnv();
///ToDo: check for leaks on /boot (i.e. indexed ntfs) with:
//	const unsigned base_mem_leakcheck = HogEnv().pd().used_ram().value;
	for( int i = 0; i < 1/*10*/; i++ )
	{
		//Genode::log( "---------------------------------------- > ", i );
		CHECK( false == BEntry("/DataNtfs/FooBar.foobar").Exists() );
	}
	
	/*
	That allowed to pinpoint a leak (fixed since): we were leaking ___2232___ bytes at each iteration:
		[init -> basic-layers] malloc_init: alloc consumes 979038 bytes
		[init -> basic-layers]  - Pass: false == BEntry("/DataNtfs/FooBar.foobar").Exists()
		[init -> basic-layers] malloc_init: alloc consumes 981270 bytes
		[init -> basic-layers]  - Pass: false == BEntry("/DataNtfs/FooBar.foobar").Exists()
		[init -> basic-layers] malloc_init: alloc consumes 983502 bytes
		[init -> basic-layers]  - Pass: false == BEntry("/DataNtfs/FooBar.foobar").Exists()
	*/
#endif
	// check layout of "raw" (below-indexer) partition (/DataNtfs)
	CHECK( false == BEntry("/DataNtfs/FooBar.foobar").Exists() );
	CHECK( true  == BEntry("/DataNtfs/_Hai_Genode_Root_Folder_").Exists() );
	
	// check layout of *indexed* partition (/boot)
	CHECK( false == BEntry("/boot/_Hai_Genode_Root_Folder_").Exists() );  // check for "chroot" -- the "secret" files should not be visible (ought to be mounted *above* the chroot folder)
	
	// Or ---> /DataNtfs/_Hai_Genode_Root_Folder_
	CHECK( B_OK == create_directory("/boot/Music", 0777) );
	// check that is also returns gracefully in already-existing case:
	CHECK( B_OK == create_directory("/boot/Music", 0777) );
	
	// BNode : xattr (extended attributes) :
	BNode invalid_node;
	CHECK( B_FILE_ERROR == invalid_node.ReadAttr("foo_attr", B_INT32_TYPE, 0, &dummy_int, sizeof(dummy_int)) );
	static char xa_name[B_ATTR_NAME_LENGTH/*512*/] = {};
#if 0  // or: "if define FS_WITHOUT_XA_SUPPORT"
use /boot instead of /raw here:
	BNode node_w_attrs( "/ram_rw/boot" );  // xattr's work even on directories (at least with NTFS)
	const BString
		out_value( "Pink Floyd" );
	CHECK( B_UNSUPPORTED == node_w_attrs.GetNextAttrName(xa_name) );  // HoG's errno is B_UNSUPPORTED (instead of B_ENTRY_NOT_FOUND in Haiku)
	CHECK( B_UNSUPPORTED == node_w_attrs.WriteAttrString("Audio:Artist", &out_value) );
	struct attr_info xa_info = {};
	CHECK( B_UNSUPPORTED == node_w_attrs.GetAttrInfo("foobar:name", &xa_info) );
	///later: improve libroot-build/AttributeDirectory so that it takes into account newly created attributes on the (still open) node ; for now we close-and-reopen the node, to force AttributeDir to flush its cache (re-read attributes from disk), otherwise the next calls to GetNextAttrName() below would fail, even though this is a "bug" we can live with.
	//CHECK(B_OK == node_w_attrs.RewindAttrs());  <== this does not work (it rewinds on an unchanged in-memory listing, unsynced with on-disk attr changes)
	node_w_attrs.Unset();
	node_w_attrs.SetTo( "/ram_rw/boot" );
	//... more read/write ops here...
	CHECK( B_ENTRY_NOT_FOUND == node_w_attrs.RemoveAttr("Audio:Artist") );///might want to implement as B_UNSUPPORTED here as well, for consistency ?
#else  // Actual Xattr support:
	// setxattr:
	const BString
		out_value( "Pink Floyd" );
	BNode node_w_attrs;
	CHECK( B_OK == node_w_attrs.SetTo("/boot/Music") );//"/ram_rw/boot") );  // xattr's work even on directories (at least with NTFS)
#if 1
	// Now test access() on dot-paths
	//
	///ToDo: that might not be representative/realistic right now as we're not yet using a vfs server, we use a _local_ FS...
	// a Genode VFS server behaves differently, it will call leaf_node() !
	// Use a sub-dir instead:
	//
	/*
	BFile crea("/boot/Music/02_hiscore.mp3", B_WRITE_ONLY | B_CREATE_FILE);
	CHECK( B_OK == node_w_attrs.SetTo("/boot/Music/02_hicore.mp3") );
	*/
	// This looks funny/un-needed, but is in fact useful, to trigger Genode VFS code paths akin to "vfs server" mode ; namely, exercice leaf_path(), down to our NTFS plug-in
	CHECK( 0==access("/boot/Music", 0777) );
	CHECK( 0==access("/boot/.Music", 0777) );
#endif
	CHECK( B_OK == node_w_attrs.WriteAttrString("Audio:Artist", &out_value) );
	
	// !
	CHECK( true == BEntry("/DataNtfs/_Hai_Genode_INDEX_v0.txt").Exists() );
	
	// getxattr: read it back, check value:
	BString in_value;
	CHECK( B_OK == node_w_attrs.ReadAttrString("Audio:Artist", &in_value) );
	CHECK( in_value == out_value );
	
	// getxattr of bogus entry: should not entail a LOG error
	CHECK( B_ENTRY_NOT_FOUND == node_w_attrs.ReadAttrString("foobar:name", &in_value) );
	
	// listxattr
	CHECK( node_w_attrs.GetNextAttrName(xa_name) == B_OK );
	CHECK( BString("Audio:Artist") == xa_name );
	CHECK( node_w_attrs.GetNextAttrName(xa_name) == B_ENTRY_NOT_FOUND );
	struct attr_info xa_info = {};
	CHECK( node_w_attrs.GetAttrInfo("foobar:name", &xa_info)  == B_ENTRY_NOT_FOUND );
	CHECK( node_w_attrs.GetAttrInfo("Audio:Artist", &xa_info) == B_OK );
	CHECK( xa_info.type == B_STRING_TYPE && xa_info.size == out_value.Length() +1 );
#endif  // ~Actual Xattr support
	
#if 1
	// sanity check the *indexer*: make sure it does not attempt to index /binary/ data (e.g. flattened messages):
	//
	// ... create a binary attribute ...
	{
		BMallocIO io;
		{
			BMessage m( 1234 );
			m.Flatten( &io );
		}
		
		CHECK( signed(io.BufferLength()) == node_w_attrs
				.WriteAttr("archived_msg", B_MESSAGE_TYPE, 0, io.Buffer(), io.BufferLength())
			);
	}
	// ... scan the index ...
	{
		BFile sanity_idx( "/DataNtfs/_Hai_Genode_INDEX_v0.txt", B_READ_ONLY );
		CHECK( B_OK == sanity_idx.InitCheck() );
		
		char buf[512] = {};
		CHECK( sanity_idx.Read( buf, sizeof(buf) ) > 0 );
		CHECK( NULL == strstr(buf, "GGSM") );  // no flattened message inside ?
		CHECK( NULL != strstr(buf, "RTSC") );  // but we DO have CSTR (strings) inside ?
	}
#endif
	
	// removexattr
	// (when the attribute exists, and again when it doesn't)
	CHECK( node_w_attrs.RemoveAttr("Audio:Artist") == B_OK );
	CHECK( node_w_attrs.RemoveAttr("Audio:Artist") == B_ENTRY_NOT_FOUND );
	
#if hog_TEST_LEVEL >= 7
	// BQuery tests
	//
	// - populate:
	BFile mpeg;
	CHECK( false == BEntry("/boot/mpeg_file").Exists() );
	CHECK( B_OK  == mpeg.SetTo("/boot/mpeg_file", B_CREATE_FILE | B_WRITE_ONLY) );
	CHECK( true  == BEntry("/boot/mpeg_file").Exists() );
	CHECK( B_OK  == mpeg.WriteAttrString("Audio:Artist", &out_value) );
	// - query: (setting predicate with RPN notation):
	BVolume q_vol;
	CHECK( B_OK == node_w_attrs.GetVolume(&q_vol) );
	BQuery query;
	CHECK( B_OK == query.SetVolume(&q_vol) );
	CHECK( B_OK == query.PushAttr("Audio:Artist") );
	CHECK( B_OK == query.PushString("Pink Floyd", false) );
	CHECK( B_OK == query.PushOp(B_EQ) );
	BString predicate;
	CHECK( B_OK == query.GetPredicate(&predicate) );
	CHECK( predicate == "(Audio:Artist==\"Pink*Floyd\")" );
	CHECK( B_OK == query.Fetch() );
//
//cannot implement BQuery cleanly as GetRootDirectory() etc does not work yet:
#if 1  //xx works better now ?
///xx Maybe hog_TEST_LEVEL 6 or even 5 ?
	BDirectory root;
	BVolume v( q_vol );
	CHECK( v.Device() == 0 );//q_vol.Device() );
	CHECK( B_OK == v.GetRootDirectory(&root) );
	{
		node_ref nr;
		CHECK( B_OK == root.GetNodeRef(&nr) );
		CHECK( 0 == nr.device );
		CHECK( 1 == nr.node );
	}
	{
		entry_ref enr;
		CHECK( B_OK == get_ref_for_path("/boot", &enr) );
		CHECK( 0==strcmp("boot", enr.name) );
///ToDo: this no longer passes, now it's node <285116912> instead...
//		CHECK( enr.device == 285176240 );  // device for "/" (not "/boot"!) (an entry_ref's directory node refers to the *parent* directory, and its name field to the actual entry !)
		CHECK( enr.directory == 1 );  // /boot is the first(? xx) child of "/"
	}
	
	{
		BEntry en;
		CHECK( B_OK == root.GetEntry(&en) );
		CHECK( en.Name() && 0==strcmp(en.Name(), "boot") );
	}
	
	BPath protocol;
	CHECK( B_OK == protocol.SetTo(&root) );
	CHECK( protocol.Leaf() && 0==strcmp(protocol.Leaf(), "boot") );
	CHECK( B_OK == protocol.Append(".BQuery.support"/***/) );
#endif
#if 1
		printf(" - - - - - - - - - -------------------------------------\n" );
	// Tracker-like scenario:
	char vname[256] = {};
	CHECK( B_OK == v.GetName(vname) );
	CHECK( 0==strcmp(vname, "boot") );
	//printf("BVolume: dev %ld <%s>\n", v.Device(), vname);
	
	// scenario, part 2:
	BDirectory original;
	CHECK( B_OK == v.GetRootDirectory(&original) );
	node_ref noderef;
	entry_ref entref;
	{
		CHECK( B_OK == original.GetNodeRef(&noderef) );
		CHECK( noderef.device == v.Device() );
		CHECK( noderef.node == 1 );
		//printf( "_ nref: dev <%ld> node <%ld>\n", noderef.device, noderef.node );
		
		BEntry entry;
		CHECK( B_OK == original.GetEntry(&entry) );
		CHECK( entry.Name() && 0==strcmp(entry.Name(), "boot") );
		//printf(" __ entry: <%s>\n", entry.Name());
		
		CHECK( B_OK == entry.GetRef(&entref) );
		CHECK( entref.name && 0==strcmp(entref.name, "boot") );
///ToDo: this no longer passes
//		CHECK( entref.device == 285176240 );  // an entry_ref's "device" field refers to the *parent directory* directory, which is "/", which is 285176240
		CHECK( entref.directory == 1 );  // the "boot" subdirectory of "/", is 1
		//printf(" __ ref: dev %ld dir %ld <%s>\n", entref.device, entref.directory, entref.name);
		
		BPath path;
		CHECK( B_OK == entry.GetPath(&path) );
		CHECK( path.Path() && 0==strcmp(path.Path(), "/boot") );
		//printf(" path.Path() : <%s>\n", path.Path());
		//used to get "//boot" here, but I fixed my over-eager optim in normalize() and now we're good
	}
	
	BDirectory clone1( &noderef );
	CHECK( B_OK == clone1.InitCheck() );
	CHECK( clone1 == original );
	BDirectory clone2( &entref );
	CHECK( B_OK == clone2.InitCheck() );
	CHECK( clone2 == original );
#endif
//
	BEntry qent;
	CHECK( B_OK == query.GetNextEntry(&qent, false) );
	CHECK( BPath(&qent).Path() == BString("/boot/mpeg_file") );
	CHECK( B_ENTRY_NOT_FOUND == query.GetNextEntry(&qent, false) );
	;
///FIXME-2: add these to make sure the query parser is lax enough to tolerate Lightning's predicate style:
//	setup_query_predicate( "(Audio:Artist == \"*[aA]*\")", query, q_vol );  // spaces around the operator
//	CHECK( .. );
	;
//	setup_query_predicate( "(Audio:Artist == *)", query, q_vol );  // spaces, and un-quotted wildcard operand
//	CHECK( .. );
//	;
	/*
	indeed, Lightning had to be adapted as we fail the first of these two test cases:
		"Audio:Artist == *"
		"(Audio:Artist == *)"  //temp, until SlotItem is improved
	*/
	// - query: (setting predicate directly):
	setup_query_predicate( "(Audio:Artist==\"Pink*Foobar\")", query, q_vol );
	CHECK( B_ENTRY_NOT_FOUND == query.GetNextEntry(&qent, false) );
	;
	// - query: (setting predicate directly):
	setup_query_predicate( "(Audio:Artist==\"Pink*F*d\")", query, q_vol );
	CHECK( B_OK == query.GetNextEntry(&qent, false) );
	;
	// - query: (setting predicate directly):
	setup_query_predicate( "(Audio:Artist==\"[pP][iI][nN][kK]*[fF][lL][oO][yY][dD]\")", query, q_vol );
	CHECK( B_OK == query.GetNextEntry(&qent, false) );
	;
	// - query: (setting predicate directly):
	setup_query_predicate( "(Audio:Artist==\"[pP][iI][nN][kK]*[fF]\")", query, q_vol );
	CHECK( B_ENTRY_NOT_FOUND == query.GetNextEntry(&qent, false) );
	;
	// remove files from matches
///FIXME-2: change test to work on a file inside /boot/Music, to be sure sub-folders work with entry_ref et al...
	CHECK( B_OK == mpeg.RemoveAttr("Audio:Artist") );
///ToDo: this test seems to be well formed, since /boot/Music is empty ? So why does Remove() fail ?
//	CHECK( B_OK == BEntry("/boot/Music").Remove() );
	// files should no longer be found
	;
	// - query: (setting predicate directly):
	setup_query_predicate( "(Audio:Artist==\"Pink*Floyd\")", query, q_vol );
	CHECK( B_ENTRY_NOT_FOUND == query.GetNextEntry(&qent, false) );

#endif
	
	// BFile
	BFile file;
	CHECK( file.InitCheck() == B_NO_INIT );
	CHECK( B_ENTRY_NOT_FOUND == file.SetTo("/ram_rw_WrongPath/newfile", B_CREATE_FILE | B_WRITE_ONLY) );
	CHECK( B_OK == file.SetTo("/boot/newfile", B_CREATE_FILE | B_WRITE_ONLY) );

	// BFile : read/write
	// write contents, read it back, check for equality:
	static  const BString payload = "test_payload";
	CHECK( payload.Length() == file.Write(payload.String(), payload.Length()) );
	BFile reader( "/boot/newfile", B_READ_ONLY );
	char buffer[ 128 ] = { 0 };
	CHECK( payload.Length() == reader.Read(buffer, 128) );
	CHECK( payload == buffer );
	
	// fs_info.c (supports BVolume, and BQuery)
	CHECK( 0 == dev_for_path("/boot") );//
	CHECK( 0 == dev_for_path("/boot/newfile") );//
		///later: dev_for_path() issues: my implementation does not set errno, and attempts to return a (signed) status_t into an (unsigned!) BSD-style dev_t ;
		/// so I actually get '2' instead of '-1' result here ; maybe break strict compatibility, change its prototype to return a signed int ? Only storage/VolumeRoster.cpp is affected though...
	//CHECK( B_ERROR/**/ == dev_for_path("/foo-bar") );
	
	// BVolume
	BVolume volume;
	BVolumeRoster volumeroster;
	CHECK( B_OK == volumeroster.GetNextVolume(&volume) );
	CHECK( 0 == volume.Device() );//
	CHECK( 512 == volume.BlockSize() );
	CHECK( B_OK == volume.InitCheck() );
	CHECK( B_OK == volume.GetName(buffer) );
	CHECK( 0 == strcmp(buffer, "boot") );//
	//-------------
	CHECK( volume.KnowsAttr() );
	CHECK( volume.KnowsQuery() );
	CHECK( volume.KnowsMime() );
	CHECK( volume.IsPersistent() );
	CHECK( false == volume.IsReadOnly() );
	CHECK( false == volume.IsRemovable() );
	//-------------
	CHECK( B_ERROR == volumeroster.GetNextVolume(&volume) );
	
	{
		BVolume boot;
		CHECK( B_OK == BVolumeRoster().GetBootVolume(&boot) );
		CHECK( 0 == boot.Device() );//
	}
	
	///+ BApp Resources.. (so that apps/clock will be able to load its clock backgrounds, and AutoCast will load its cc.png updater icon, and Deskbar/Tracker will show icons...)
}

#endif  // ~hog_TEST_LEVEL >= 3


//#pragma mark - t5

/***********************/
#if hog_TEST_LEVEL >= 5

// libbe.so
#include <app/Looper.h>
#include <app/MessageRunner.h>
#include <interface/HaikuControlLook.h>
#include <interface/Screen.h>
#include <interface/Window.h>
#include <storage/MimeType.h>


class SimpleHandler : public BHandler
{
public:
		SimpleHandler()
		:	BHandler()
			,received( 0 )
		{
		}
		
		void MessageReceived( BMessage * msg ) override
		{
			switch( msg->what )
			{
				case 0x1234:
					//printf( "  Received 0x1234 message !\n" );
					received += 1;
if( received == 1 )
{
	BMessenger mg( this );
	mg.SendMessage( 0x2222 );
}
				break;
				
				case 0x2222:
//seems to work fine... ??
// maybe the Mandelbrot problems is with _Run()_ timing, not with BMessenger?
// yep indeed ! remove this test: ?
Genode::log("..Pass'ed BMessenger.this test");
				break;
				
				default:
					BHandler::MessageReceived( msg );
			}
		}

public:  // Data (!)
		int received;
};

static
void testBehemoth()
{
	BAIL_OUT_OR_PROCEED
	printf( "- App/Interface Kit tests -----------------\n" );
	
	/*** App Kit ***/
	
	/******/
	// del this, now testing in broker-tests instead:
	//BMessenger("application/x-vnd.test").SendMessage(B_UNDO);
	// NULL be_roster.. Hence it crashes in MessagePrivate.h:193 ... if get_port_info (after find_port) returns in error
	/*
	[init -> basic-layers] Warning: find_port( system:launch_daemon ): n/i
	[init -> basic-layers]  ::Create_Port( tmp_reply_port ) => __0__
	[init -> basic-layers] Warning:  ::set_port_owner: 0
	[init -> basic-layers]   delete_port: 0
	no RM attachment (READ pf_addr=0x4 pf_ip=0x137007c from pager_object: pd='init -> basic-layers' thread='pthread.0') 
	*/
	/******/
	
	BMessage message;
	message.AddInt32( "mouse_clicks", 2 );
	CHECK( message.FindInt32("mouse_clicks") == 2 );
	
	const bigtime_t interval = 100*1000;
	const int fires_count = 4;
	;
	BLooper * looper = new BLooper;  // Haiku won't allow stack-allocated loopers
	CHECK( looper != NULL );
	SimpleHandler * handler = new SimpleHandler;  // and handlers are *owned* (deleted) by their looper...
	looper->AddHandler( handler );
	CHECK( looper->Run() >= 0 );  // valid thread_id
CHECK( B_OK == snooze(1000) );  // 1 ms
	;
	BMessenger messenger( handler );
	CHECK( messenger.IsValid() );
CHECK( B_OK == snooze(1001) );  // 1 ms
	;
//+	BMessageRunner runner_with_count( messenger, 0x1, interval, 1 );
	{
		BMessageRunner runner( messenger, 0x1234, interval );
CHECK( B_OK == snooze(1002) );  // 1 ms
		CHECK( B_OK == runner.InitCheck() );
		;
	// give the runners time to run ; the snooze, combined with the count
	// check, indeed verifies that we received the expected msgs in the expected time.
//CHECK( B_OK == snooze(1003) );  // 1 ms
		snooze( interval * (fires_count -1) * 1.1 );  // add 10% sleeping, to make sure all 4 messages have time to make it
		///xx not clear on why I need the "-1" here.. Is the first message sent immediately without waiting ?!
CHECK( B_OK == snooze(1000) );  // 1 ms
	}
	CHECK( handler->received == fires_count );
	
	// BApplication : see BWindow test below
	
	
	/*** Storage Kit ***/
	
	BMimeType check_signature_1( "" );//"dummy" );
	BMimeType check_signature_2( "application/x-vnd.vendorname-appname" );
	CHECK( check_signature_1.IsValid() == false );
	CHECK( check_signature_2.IsValid() == true );
	
	
	/*** Interface Kit ***/
	
	// a BApp is a pre-requisite for BWindow, for system_colors() etc
	BApplication application( "application/x-vnd.hog-test" );
	CHECK( be_app != NULL );
	CHECK( B_CURSOR_SYSTEM_DEFAULT != NULL );
	
	CHECK( be_control_look != NULL );
	CHECK( system_colors() != NULL );
	
	//BPoint, BRect
	
	BScreen screen;
	CHECK( screen.Frame().Width() == 1920. -1. );  // correct in Qemu when in USE_DRV_MANAGER mode
	
	//BBitmap bitmap;
	
	//BFont
	
	CHECK( be_app );
	
	// BWindow : test for cap leaks or mem leaks
	//Genode::log( "-- BWindow (T- used caps/mem:  ", be_app->Env().pd().used_caps().value, ", ", be_app->Env().pd().used_ram().value, " bytes)" );
	{
		const size_t caps_base_leakcheck = be_app->Env().pd().used_caps().value;
		
		BWindow * temp = new BWindow( BRect(10,10,600,400), "tempwindow", B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS );
		temp->Show();
		snooze( 399000 );  // give it some time to Run() the thread
		//Genode::log( "-- BWindow (T0 used caps/mem:  ", be_app->Env().pd().used_caps().value, ", ", be_app->Env().pd().used_ram().value, " bytes)" );
		CHECK( true == temp->Lock() );
// deadlocking again, in jam t8, wait()ing for "forward-genode_events-to-haiku"
//UPD: not seen any more after I reinforced (again) the tear-down checks in forwarder/bridger..?
//Genode::error("---------------------------------------------");
		temp->Quit();
		CHECK( B_OK == snooze(399000) /* consolidate Quit().. */ );  // give it some time to clean-up "zombie" threads?
		
		// this is where caps may go e.g. from 56 to 57 if there is a leak (a leaked Thread counts for 1 cap and ca. 8 KB RAM)
		CHECK( be_app->Env().pd().used_caps().value == caps_base_leakcheck );
	}
	//Genode::log( "-- BWindow (T+ used caps/mem:  ", be_app->Env().pd().used_caps().value, ", ", be_app->Env().pd().used_ram().value, " bytes)" );
	
	// BWindow : basics
	BWindow * window = new BWindow(
		BRect( 100., 100., 500., 90. ),
		"test window_0",
		B_DOCUMENT_WINDOW,
		B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE
		);
	window->Show();
	CHECK( BString("test window_0") == window->Title() );
	CHECK( window->Flags() == (B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE) );
	
	/*** Epilogue ***/
	
	CHECK( window->Lock() == true );
	window->Quit();
	CHECK( looper->Lock() == true );
	looper->Quit();
}

#endif




//#pragma mark - t7

/***********************/
#if hog_TEST_LEVEL >= 7

// libmedia.so
#include <media/MediaFile.h>

static
void testMedia()
{
	BAIL_OUT_OR_PROCEED
	/*** Media Kit ***/
	
	printf( "- Media Kit tests -----------------\n" );
	
	entry_ref ref;
	get_ref_for_path( "/beos.mp3", &ref );
	BMediaFile ctor_test( &ref );
}


// libbe.so
#include <support/Url.h>

static
void testSeven()
{
	BAIL_OUT_OR_PROCEED
	/*** Interface Kit ***/
	
	printf( "- Interface Kit (lev. 7) tests -----------------\n" );
	
	{
		BApplication application( "application/x-vnd.hog-test" );
		CHECK( be_app );
	}
	CHECK( be_app == NULL );  // !! reset by dtor, eh ? so that explains why be_app is NULL in places
	//+	BApplication application( "application/x-vnd.hog-test" );
	// make sure we are not running without the previous part of the test and its init
	//+ CHECK( be_app );
	
	BSlider slider(
		BRect(), "slider-view", "Slider", NULL, 1, 100
		);
	slider.SetLabel( "modified" );
	CHECK( slider.Label() == BString("modified") );
	
	/*** Support Kit ***/
	
	printf( "- Support Kit (lev. 7) tests -----------------\n" );
	
	BUrl url1( "blah" );
	BUrl url2( "http://genode.org" );
	CHECK( url1.IsValid() == false );
	CHECK( url2.IsValid() == true );
}

#endif



//#pragma mark - t8

/***********************/
#if hog_TEST_LEVEL >= 8

// libtracker.so
#include <FilePanel.h>

static
void testLibTracker()
{
	BAIL_OUT_OR_PROCEED
	BApplication * application = new BApplication( "application/x-vnd.hog-test-libtracker" );
	
	class TestView : public BView
	{
		/* Unit test for _CanvasGenode, Arcs, and StrokeLine() (detect missing pixel
		at the end of the painted line -- run this test and use Magnify to inspect pixels) */
		
	public:
		TestView()
		:	BView( BRect(0, 0, 99, 99), "testview", B_FOLLOW_NONE, B_WILL_DRAW )
		{
		}
		
		void Draw( BRect )
		{
			SetHighColor( 0, 0, 0 );
			
			// 4x4 rect
			BRect r4( 10, 10, 13, 13 );
			FillRect( r4 );
			
			// separated-by-1px liséré, interrupted by 1px gap at each corner
			BRect lis;
			{
				lis = r4;
				lis.InsetBy( -2, -2 );
			}
			
			StrokeLine(
				lis.LeftTop(),
				lis.RightTop() - BPoint(2,0)
				);
			StrokeLine(
				lis.RightTop(),
				lis.RightBottom() - BPoint(0, 2)
				);
			StrokeLine(
				lis.RightBottom(),
				lis.LeftBottom() + BPoint(2,0)
				);
			StrokeLine(
				lis.LeftBottom(),
				lis.LeftTop() + BPoint(0, 2)
				);
			
			// test single-pixel painting (detect e.g. division-by-zero)
			SetHighColor( 250, 0, 0 );
			StrokeLine( lis.RightTop(), lis.RightTop() );
			
			// *RoundRect()
			//
			
			BRect r1( 10., 10., 60., 60. );
			
			r1.OffsetBy( 100., 0. );
			StrokeRoundRect( r1, 10., 10. );
			
			// visual debugging: highlight "un-touched" pixels:
			SetHighColor( 0, 250, 0 );
			StrokeLine( BPoint(250, 5), BPoint(250, 70) );
			SetHighColor( 250, 0, 0 );
			// ~ visual debugging: highlight "un-touched" pixels
			
			r1.OffsetBy( 100., 0. );
			FillRoundRect( r1, 10., 10. );
			
			/* --- Line 2 --- */
			
			// test the ellipse/arc painting algorithm
			BRect r( 0., 0., 50., 50 );
			r.OffsetBy( 10., 100. );
			
			FillEllipse( r );
			r.OffsetBy( r.Width() +10., 0. );
			
			StrokeEllipse( r );
			r.OffsetBy( r.Width() +10., 0. );
			
			const int increment_start = 45;//30;
			const float span = 270;//90;//30.;
			
			// start at -45°/2 to somewhat simulate tts pie menus
			for( int i = -45/2; i <= 360; i += increment_start )
			{
				// upper row: StrokeArc
				StrokeRect( r );
				StrokeArc ( r, i, span );
				
				{
					// lower row: FillArc
					BRect r2 = r.OffsetByCopy( 0., r.Height() +10. );
					StrokeRect( r2 );
					FillArc ( r2, i, span );
				}
				
				r.OffsetBy( r.Width() +10., 0. );
			}
		}
	};
	
	BWindow * window = new BWindow(
		BRect( 30., 350., 750., 750. ),
		"test window",
		B_DOCUMENT_WINDOW,
		B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE
		);
	window->AddChild( new TestView );
	window->ChildAt(0)->ResizeTo( window->Size() );
	window->Show();
	
	//for(;;) snooze(99000);
	
	/*********/
	
	BFilePanel * panel = new BFilePanel;
	panel->Show();
	panel->Window()->UpdateIfNeeded();///
	
	/*********/
	
#if 1
	snooze(4555000);
	///+Quit()/delete..
	delete application;
	application = NULL;
#endif
}

#endif  // ~ hog_TEST_LEVEL >= 8




//#pragma mark - main() -


#if hog_TEST_LEVEL == 0  // !

#if 0
// base.lib.a
#	include <base/component.h>
void Component::construct( Genode::Env & /*env*/ )
{
	Genode::log( "------- BARE BONES: Component::construct entry -------" );
}
#else
// libc.lib.so
#	include <libc/component.h>  // Libc::Component::construct()
void Libc::Component::construct( Libc::Env & /*env*/ )
{
	Genode::log( "------- Almost-Bare-Bones: Libc::Component::construct entry -------" );
}
#endif

#else  // hog_TEST_LEVEL > 0 :

int main()
{
	Genode::log( "------- main() entry -------" );
	
	testkernel();
	
#if hog_TEST_LEVEL >= 1
	printf( "---------- Running tests... ----------\n" );
	
	testSupportKit();
	
	// For our Genode-specific "registrar":
	//testStoragePipes();
#endif
	
#if hog_TEST_LEVEL >= 2
	testTranslationKit();
#endif
	
#if hog_TEST_LEVEL >= 4
	testStorage_with_libc( "/ram_rw" );    // simple file system
	testStorage_with_libc( "/DataNtfs" );  // NTFS
	testStorage_with_libc( "/boot" );      // NTFS + indexer
	
	//for( int i = 0; i < 4; i++ )  // to amplify possible leaks
	{
		testStorageKit_ram();
		testStorageKit_ntfs();
		testStorageKit_ntfs_MemoryLeak();
	}
	
	// Real-world test, TTS/MMD:
	testStorageMMD();
#endif
	
#if hog_TEST_LEVEL >= 5
	testBehemoth();
#endif
	
#if hog_TEST_LEVEL >= 7
	testSeven();
	testMedia();
#endif
	
#if hog_TEST_LEVEL >= 8
	testLibTracker();
#endif
	
#if hog_TEST_LEVEL >= 1
	printf( "---------- Tests complete! ----------\n" );
#endif
	
	return 0;
}


#endif


