#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <cerrno>
#include <sys/types.h>
#include <signal.h>
#include <utime.h>

/**
 * This file is used as a template to test the different ApplicationPool::Interface implementations.
 * It is #included in ApplicationPool_PoolTest.cpp and ApplicationPool_Server_PoolTest.cpp
 */
#ifdef USE_TEMPLATE
	static void sendTestRequest(SessionPtr &session, const char *uri = "/foo/new") {
		string headers;
		#define ADD_HEADER(name, value) \
			headers.append(name); \
			headers.append(1, '\0'); \
			headers.append(value); \
			headers.append(1, '\0')
		ADD_HEADER("HTTP_HOST", "www.test.com");
		ADD_HEADER("QUERY_STRING", "");
		ADD_HEADER("REQUEST_URI", uri);
		ADD_HEADER("REQUEST_METHOD", "GET");
		ADD_HEADER("REMOTE_ADDR", "localhost");
		ADD_HEADER("SCRIPT_NAME", "");
		ADD_HEADER("PATH_INFO", uri);
		ADD_HEADER("PASSENGER_CONNECT_PASSWORD", session->getConnectPassword());
		session->sendHeaders(headers);
	}
	
	static SessionPtr spawnRackApp(ApplicationPool::Ptr pool, const char *appRoot) {
		PoolOptions options;
		options.appRoot = appRoot;
		options.appType = "rack";
		return pool->get(options);
	}
	
	static SessionPtr spawnWsgiApp(ApplicationPool::Ptr pool, const char *appRoot) {
		PoolOptions options;
		options.appRoot = appRoot;
		options.appType = "wsgi";
		return pool->get(options);
	}
	
	namespace {
		class ReloadLoggingSpawnManager: public SpawnManager {
		public:
			vector<string> reloadLog;
			
			ReloadLoggingSpawnManager(const string &spawnServerCommand,
				const ServerInstanceDir::GenerationPtr &generation,
				const AccountsDatabasePtr &accountsDatabase = AccountsDatabasePtr(),
				const string &rubyCommand = "ruby")
			: SpawnManager(spawnServerCommand, generation, accountsDatabase, rubyCommand)
			{ }
			
			virtual void reload(const string &appRoot) {
				reloadLog.push_back(appRoot);
				SpawnManager::reload(appRoot);
			}
		};
		
		struct SpawnRackAppFunction {
			ApplicationPool::Ptr pool;
			bool *done;
			SessionPtr *session;
			
			SpawnRackAppFunction() {
				done    = NULL;
				session = NULL;
			}

			void operator()() {
				PoolOptions options;
				options.appRoot = "stub/rack";
				options.appType = "rack";
				options.useGlobalQueue = true;
				SessionPtr session = pool->get(options);
				*done = true;
				if (this->session != NULL) {
					*this->session = session;
				}
			}
		};
	}
	
	TEST_METHOD(1) {
		// Calling ApplicationPool.get() once should return a valid Session.
		SessionPtr session(spawnRackApp(pool, "stub/rack"));
		sendTestRequest(session);
		session->shutdownWriter();

		int reader = session->getStream();
		string result(readAll(reader));
		session->closeStream();
		ensure(result.find("hello <b>world</b>") != string::npos);
	}
	
	TEST_METHOD(2) {
		// Verify that the pool spawns a new app, and that
		// after the session is closed, the app is kept around.
		SessionPtr session(spawnRackApp(pool, "stub/rack"));
		ensure_equals("Before the session was closed, the app was busy", pool->getActive(), 1u);
		ensure_equals("Before the session was closed, the app was in the pool", pool->getCount(), 1u);
		session.reset();
		ensure_equals("After the session is closed, the app is no longer busy", pool->getActive(), 0u);
		ensure_equals("After the session is closed, the app is kept around", pool->getCount(), 1u);
	}
	
	TEST_METHOD(3) {
		// If we call get() with an application root, then we close the session,
		// and then we call get() again with the same app group name,
		// then the pool should not have spawned more than 1 app in total.
		SessionPtr session(spawnRackApp(pool, "stub/rack"));
		session.reset();
		session = spawnRackApp(pool, "stub/rack");
		ensure_equals(pool->getCount(), 1u);
	}
	
	TEST_METHOD(4) {
		// If we call get() with an app group name, then we call get() again before closing
		// the session, then the pool will eventually have spawned 2 apps in total.
		SessionPtr session(spawnRackApp(pool, "stub/rack"));
		SessionPtr session2(spawnRackApp(pool2, "stub/rack"));
		EVENTUALLY(5,
			result = pool->getCount() == 2u;
		);
	}
	
	TEST_METHOD(5) {
		// If we call get() twice with different app group names,
		// then the pool should spawn two different apps.
		TempDirCopy c1("stub/rack", "rackapp1.tmp");
		TempDirCopy c2("stub/rack", "rackapp2.tmp");
		replaceStringInFile("rackapp2.tmp/config.ru", "world", "world 2");
		SessionPtr session = spawnRackApp(pool, "rackapp1.tmp");
		SessionPtr session2 = spawnRackApp(pool2, "rackapp2.tmp");
		ensure_equals("Before the sessions were closed, both apps were busy", pool->getActive(), 2u);
		ensure_equals("Before the sessions were closed, both apps were in the pool", pool->getCount(), 2u);
		
		sendTestRequest(session);
		string result = readAll(session->getStream());
		ensure("Session 1 belongs to the correct app", result.find("hello <b>world</b>") != string::npos);
		session.reset();
		
		sendTestRequest(session2);
		result = readAll(session2->getStream());
		ensure("Session 2 belongs to the correct app", result.find("hello <b>world 2</b>") != string::npos);
		session2.reset();
	}
	
	TEST_METHOD(6) {
		// If we call get() twice with different app group names,
		// and we close both sessions, then both 2 apps should still
		// be in the pool.
		TempDirCopy c1("stub/rack", "rackapp1.tmp");
		TempDirCopy c2("stub/rack", "rackapp2.tmp");
		SessionPtr session(spawnRackApp(pool, "rackapp1.tmp"));
		SessionPtr session2(spawnRackApp(pool, "rackapp2.tmp"));
		session.reset();
		session2.reset();
		ensure_equals("There are 0 active apps", pool->getActive(), 0u);
		ensure_equals("There are 2 apps in total", pool->getCount(), 2u);
	}
	
	TEST_METHOD(7) {
		// If we call get() even though the pool is already full
		// (active == max), and the app group name is already
		// in the pool, then the pool must wait until there's an
		// inactive application.
		pool->setMax(1);
		// TODO: How do we test this?
	}
	
	TEST_METHOD(8) {
		// If ApplicationPool spawns a new instance,
		// and we kill it, then the next get() with the
		// same application root should not throw an exception:
		// ApplicationPool should spawn a new instance
		// after detecting that the original one died.
		SessionPtr session = spawnRackApp(pool, "stub/rack");
		kill(session->getPid(), SIGKILL);
		session.reset();
		usleep(20000); // Give the process some time to exit.
		spawnRackApp(pool, "stub/rack"); // should not throw
	}
	
	struct PoolWaitTestThread {
		ApplicationPool::Ptr pool;
		SessionPtr &m_session;
		bool &m_done;
		
		PoolWaitTestThread(const ApplicationPool::Ptr &pool,
			SessionPtr &session,
			bool &done)
		: m_session(session), m_done(done) {
			this->pool = pool;
			done = false;
		}
		
		void operator()() {
			m_session = spawnWsgiApp(pool, "stub/wsgi");
			m_done = true;
		}
	};

	TEST_METHOD(9) {
		// If we call get() even though the pool is already full
		// (active == max), and the app group name is *not* already
		// in the pool, then the pool will wait until enough sessions
		// have been closed.
		
		// Make the pool full.
		pool->setMax(2);
		SessionPtr session1 = spawnRackApp(pool, "stub/rack");
		SessionPtr session2 = spawnRackApp(pool2, "stub/rack");
		EVENTUALLY(5,
			result = pool->getCount() == 2u;
		);
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u;
		);
		session1 = spawnRackApp(pool, "stub/rack");
		session2 = spawnRackApp(pool2, "stub/rack");
		ensure_equals(pool->getActive(), 2u);
		
		// Now spawn an app with a different app root.
		SessionPtr session3;
		bool done;
		TempThread thr(PoolWaitTestThread(pool2, session3, done));
		usleep(500000);
		ensure("ApplicationPool is still waiting", !done);
		ensure_equals(pool->getActive(), 2u);
		ensure_equals(pool->getCount(), 2u);
		
		// Now release one slot from the pool.
		session1.reset();
		
		// Session 3 should eventually be opened.
		EVENTUALLY(10,
			result = done;
		);
		ensure_equals(pool->getActive(), 2u);
		ensure_equals(pool->getCount(), 2u);
	}
	
	TEST_METHOD(10) {
		// If we call get(), and:
		// * the pool is already full, but there are inactive apps
		//   (active < count && count == max)
		// and
		// * the app group name for this get() is *not* already in the pool
		// then the an inactive app should be killed in order to
		// satisfy this get() command.
		TempDirCopy c1("stub/rack", "rackapp1.tmp");
		TempDirCopy c2("stub/rack", "rackapp2.tmp");
		
		// Make the pool full.
		pool->setMax(2);
		SessionPtr session1 = spawnRackApp(pool, "rackapp1.tmp");
		SessionPtr session2 = spawnRackApp(pool2, "rackapp1.tmp");
		EVENTUALLY(5,
			result = pool->getCount() == 2u;
		);
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u;
		);
		
		// Now spawn a different app.
		session1 = spawnRackApp(pool, "rackapp2.tmp");
		ensure_equals(pool->getActive(), 1u);
		ensure_equals(pool->getCount(), 2u);
	}
	
	TEST_METHOD(11) {
		// A Session should still be usable after the pool has been destroyed.
		SessionPtr session = spawnRackApp(pool, "stub/rack");
		pool->clear();
		pool.reset();
		pool2.reset();
		
		sendTestRequest(session);
		session->shutdownWriter();
		
		int reader = session->getStream();
		string result = readAll(reader);
		session->closeStream();
		ensure(result.find("hello <b>world</b>") != string::npos);
	}
	
	TEST_METHOD(12) {
		// If tmp/restart.txt didn't exist but has now been created,
		// then the applications under app_root should be restarted.
		struct stat buf;
		TempDirCopy c("stub/rack", "rackapp.tmp");
		SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
		SessionPtr session2 = spawnRackApp(pool2, "rackapp.tmp");
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getCount() == 2u;
		);
		
		touchFile("rackapp.tmp/tmp/restart.txt");
		spawnRackApp(pool, "rackapp.tmp");
		
		ensure_equals("No apps are active", pool->getActive(), 0u);
		ensure_equals("Both apps are killed, and a new one was spawned",
			pool->getCount(), 1u);
		ensure("Restart file still exists",
			stat("rackapp.tmp/tmp/restart.txt", &buf) == 0);
	}
	
	TEST_METHOD(13) {
		// If tmp/restart.txt was present, and its timestamp changed
		// since the last check, then the applications under the app group name
		// should still be restarted. However, a subsequent get()
		// should not result in a restart.
		pid_t old_pid;
		TempDirCopy c("stub/rack", "rackapp.tmp");
		TempDir d("rackapp.tmp/tmp/restart.txt");
		SessionPtr session = spawnRackApp(pool, "rackapp.tmp");
		old_pid = session->getPid();
		session.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u;
		);
		
		touchFile("rackapp.tmp/tmp/restart.txt", 10);
		
		session = spawnRackApp(pool, "rackapp.tmp");
		ensure("The app was restarted", session->getPid() != old_pid);
		old_pid = session->getPid();
		session.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u;
		);
		
		session = spawnRackApp(pool, "rackapp.tmp");
		ensure_equals("The app was not restarted",
			old_pid, session->getPid());
	}
	
	TEST_METHOD(15) {
		// Test whether restarting with restart.txt really results in code reload.
		TempDirCopy c("stub/rack", "rackapp.tmp");
		SessionPtr session = spawnRackApp(pool, "rackapp.tmp");
		sendTestRequest(session);
		string result = readAll(session->getStream());
		ensure(result.find("hello <b>world</b>") != string::npos);
		session.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u;
		);
		
		touchFile("rackapp.tmp/tmp/restart.txt");
		replaceStringInFile("rackapp.tmp/config.ru", "world", "world 2");
		
		session = spawnRackApp(pool, "rackapp.tmp");
		sendTestRequest(session);
		result = readAll(session->getStream());
		ensure("App code has been reloaded", result.find("hello <b>world 2</b>") != string::npos);
	}
	
	TEST_METHOD(16) {
		// If tmp/always_restart.txt is present and is a file,
		// then the application under app_root should be always restarted.
		struct stat buf;
		pid_t old_pid;
		TempDirCopy c("stub/rack", "rackapp.tmp");
		SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
		SessionPtr session2 = spawnRackApp(pool2, "rackapp.tmp");
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u && pool->getCount() == 2u;
		);
		
		touchFile("rackapp.tmp/tmp/always_restart.txt");
		
		// This get() results in a restart.
		session1 = spawnRackApp(pool, "rackapp.tmp");
		old_pid = session1->getPid();
		session1.reset();
		EVENTUALLY(5,
			// First restart: no apps are active
			result = pool->getActive() == 0u;
		);
		ensure_equals("First restart: the first 2 apps were killed, and a new one was spawned",
			pool->getCount(), 1u);
		ensure("always_restart file has not been deleted",
			stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
		
		// This get() results in a restart as well.
		session1 = spawnRackApp(pool, "rackapp.tmp");
		ensure(old_pid != session1->getPid());
		session1.reset();
		EVENTUALLY(5,
			// Second restart: no apps are active
			result = pool->getActive() == 0u;
		);
		ensure_equals("Second restart: the last app was killed, and a new one was spawned",
			pool->getCount(), 1u);
		ensure("always_restart file has not been deleted",
			stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
	}
	
	TEST_METHOD(17) {
		// If tmp/always_restart.txt is present and is a directory,
		// then the application under app_root should be always restarted.
		struct stat buf;
		pid_t old_pid;
		TempDirCopy c("stub/rack", "rackapp.tmp");
		SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
		SessionPtr session2 = spawnRackApp(pool2, "rackapp.tmp");
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u && pool->getCount() == 2u;
		);
		
		TempDir d("rackapp.tmp/tmp/always_restart.txt");
		
		// This get() results in a restart.
		session1 = spawnRackApp(pool, "rackapp.tmp");
		old_pid = session1->getPid();
		session1.reset();
		EVENTUALLY(5,
			// First restart: no apps are active
			result = pool->getActive() == 0u;
		);
		ensure_equals("First restart: the first 2 apps were killed, and a new one was spawned",
			pool->getCount(), 1u);
		ensure("always_restart directory has not been deleted",
			stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
		
		// This get() results in a restart as well.
		session1 = spawnRackApp(pool, "rackapp.tmp");
		ensure(old_pid != session1->getPid());
		session1.reset();
		EVENTUALLY(5,
			// Second restart: no apps are active
			result = pool->getActive() == 0u;
		);
		ensure_equals("Second restart: the last app was killed, and a new one was spawned",
			pool->getCount(), 1u);
		ensure("always_restart directory has not been deleted",
			stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
	}
	
	TEST_METHOD(18) {
		// Test whether restarting with tmp/always_restart.txt really results in code reload.
		TempDirCopy c("stub/rack", "rackapp.tmp");
		SessionPtr session = spawnRackApp(pool, "rackapp.tmp");
		sendTestRequest(session);
		string result = readAll(session->getStream());
		ensure(result.find("hello <b>world</b>") != string::npos);
		session.reset();

		touchFile("rackapp.tmp/tmp/always_restart.txt");
		replaceStringInFile("rackapp.tmp/config.ru", "world", "world 2");
		
		session = spawnRackApp(pool, "rackapp.tmp");
		sendTestRequest(session);
		result = readAll(session->getStream());
		ensure("App code has been reloaded (1)", result.find("hello <b>world 2</b>") != string::npos);
		session.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u;
		);
		
		replaceStringInFile("rackapp.tmp/config.ru", "world 2", "world 3");
		session = spawnRackApp(pool, "rackapp.tmp");
		sendTestRequest(session);
		result = readAll(session->getStream());
		ensure("App code has been reloaded (2)", result.find("hello <b>world 3</b>") != string::npos);
		session.reset();
	}
	
	TEST_METHOD(19) {
		// If tmp/restart.txt and tmp/always_restart.txt are present, 
		// the application under app_root should still be restarted and
		// both files must be kept.
		pid_t old_pid, pid;
		struct stat buf;
		TempDirCopy c("stub/rack", "rackapp.tmp");
		SessionPtr session1 = spawnRackApp(pool, "rackapp.tmp");
		SessionPtr session2 = spawnRackApp(pool2, "rackapp.tmp");
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u && pool->getCount() == 2u;
		);
		
		touchFile("rackapp.tmp/tmp/restart.txt");
		touchFile("rackapp.tmp/tmp/always_restart.txt");
		
		old_pid = spawnRackApp(pool, "rackapp.tmp")->getPid();
		ensure("always_restart.txt file has not been deleted",
			stat("rackapp.tmp/tmp/always_restart.txt", &buf) == 0);
		ensure("restart.txt file has not been deleted",
			stat("rackapp.tmp/tmp/restart.txt", &buf) == 0);
		EVENTUALLY(5,
			result = pool->getActive() == 0u;
		);
		
		pid = spawnRackApp(pool, "rackapp.tmp")->getPid();
		ensure("The app was restarted", pid != old_pid);
	}
	
	TEST_METHOD(20) {
		// It should look for restart.txt in the directory given by
		// the restartDir option, if available.
		struct stat buf;
		char path[1024];
		PoolOptions options("stub/rack");
		options.appType = "rack";
		options.restartDir = string(getcwd(path, sizeof(path))) + "/stub/rack";
		
		SessionPtr session1 = pool->get(options);
		SessionPtr session2 = pool2->get(options);
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u && pool->getCount() == 2u;
		);
		
		DeleteFileEventually f("stub/rack/restart.txt");
		touchFile("stub/rack/restart.txt");
		
		pool->get(options);
		
		ensure_equals("No apps are active", pool->getActive(), 0u);
		ensure_equals("Both apps are killed, and a new one was spawned",
			pool->getCount(), 1u);
		ensure("Restart file still exists",
			stat("stub/rack/restart.txt", &buf) == 0);
	}
	
	TEST_METHOD(21) {
		// restartDir may also be a directory relative to the
		// application root.
		struct stat buf;
		PoolOptions options("stub/rack");
		options.appType = "rack";
		options.restartDir = "public";
		
		SessionPtr session1 = pool->get(options);
		SessionPtr session2 = pool2->get(options);
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u && pool->getCount() == 2u;
		);
		
		DeleteFileEventually f("stub/rack/public/restart.txt");
		touchFile("stub/rack/public/restart.txt");
		
		pool->get(options);
		
		ensure_equals("No apps are active", pool->getActive(), 0u);
		ensure_equals("Both apps are killed, and a new one was spawned",
			pool->getCount(), 1u);
		ensure("Restart file still exists",
			stat("stub/rack/public/restart.txt", &buf) == 0);
	}
	
	TEST_METHOD(22) {
		// The cleaner thread should clean idle applications.
		pool->setMaxIdleTime(1);
		spawnRackApp(pool, "stub/rack");
		EVENTUALLY(10,
			result = pool->getCount() == 0u;
		);
		
		time_t begin = time(NULL);
		while (pool->getCount() == 1u && time(NULL) - begin < 10) {
			usleep(100000);
		}
		ensure_equals("App should have been cleaned up", pool->getCount(), 0u);
	}
	
	TEST_METHOD(23) {
		// MaxPerApp is respected.
		pool->setMax(3);
		pool->setMaxPerApp(1);
		
		// We connect to stub/rack while it already has an instance with
		// 1 request in its queue. Assert that the pool doesn't spawn
		// another instance.
		SessionPtr session1 = spawnRackApp(pool, "stub/rack");
		SessionPtr session2 = spawnRackApp(pool2, "stub/rack");
		
		// We connect to stub/wsgi. Assert that the pool spawns a new
		// instance for this app.
		TempDirCopy c("stub/wsgi", "wsgiapp.tmp");
		ApplicationPool::Ptr pool3 = newPoolConnection();
		SessionPtr session3 = spawnWsgiApp(pool3, "wsgiapp.tmp");
		ensure_equals(pool->getCount(), 2u);
	}
	
	TEST_METHOD(24) {
		// Application instance is shutdown after 'maxRequests' requests.
		PoolOptions options("stub/rack");
		int reader;
		pid_t originalPid;
		SessionPtr session;
		
		options.appType = "rack";
		options.maxRequests = 4;
		pool->setMax(1);
		session = pool->get(options);
		originalPid = session->getPid();
		session.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u;
		);
		
		for (unsigned int i = 0; i < 4; i++) {
			session = pool->get(options);
			sendTestRequest(session);
			session->shutdownWriter();
			reader = session->getStream();
			readAll(reader);
			// Must explicitly call reset() here because we
			// want to close the session right now.
			session.reset();
			EVENTUALLY(5,
				result = pool->getActive() == 0u;
			);
		}
		
		session = pool->get(options);
		ensure(session->getPid() != originalPid);
	}
	
	TEST_METHOD(25) {
		// If global queueing mode is enabled, then get() waits until
		// there's at least one idle backend process for this application
		// domain.
		pool->setMax(1);
		
		PoolOptions options;
		options.appRoot = "stub/rack";
		options.appType = "rack";
		options.useGlobalQueue = true;
		SessionPtr session = pool->get(options);
		
		bool done = false;
		SpawnRackAppFunction func;
		func.pool = pool2;
		func.done = &done;
		TempThread thr(func);
		
		// Previous session hasn't been closed yet, so pool should still
		// be waiting.
		usleep(100000);
		ensure("(1)", !done);
		ensure_equals("(2)", pool->getGlobalQueueSize(), 1u);
		ensure_equals("(3)", pool->getActive(), 1u);
		ensure_equals("(4)", pool->getCount(), 1u);
		
		// Close the previous session. The thread should now finish.
		session.reset();
		EVENTUALLY(5,
			result = done;
		);
	}
	
	TEST_METHOD(26) {
		// When a previous application group spinned down, and we touched
		// restart.txt and try to spin up a new process for this domain,
		// then any ApplicationSpawner/FrameworkSpawner processes should be
		// killed first.
		SessionPtr session;
		TempDirCopy c1("stub/rack", "rackapp1.tmp");
		TempDirCopy c2("stub/rack", "rackapp2.tmp");
		shared_ptr<ReloadLoggingSpawnManager> spawnManager(
			new ReloadLoggingSpawnManager("../helper-scripts/passenger-spawn-server", generation)
		);
		reinitializeWithSpawnManager(spawnManager);
		
		pool->setMax(1);
		session = spawnRackApp(pool, "rackapp1.tmp");
		session.reset();
		session = spawnRackApp(pool, "rackapp2.tmp");
		ensure_equals("rackapp2.tmp is not reloaded because restart.txt is not touched",
			spawnManager->reloadLog.size(), 0u);
		session.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u;
		);
		
		touchFile("rackapp1.tmp/tmp/restart.txt");
		session = spawnRackApp(pool, "rackapp1.tmp");
		ensure_equals("rackapp1.tmp is reloaded because restart.txt is touched (1)",
			spawnManager->reloadLog.size(), 1u);
		ensure_equals("rackapp1.tmp is reloaded because restart.txt is touched (2)",
			spawnManager->reloadLog[0], "rackapp1.tmp");
	}
	
	TEST_METHOD(27) {
		// Test inspect()
		SessionPtr session1 = spawnRackApp(pool, "stub/rack");
		string str = pool->inspect();
		ensure("Contains 'max = '", str.find("max ") != string::npos);
		ensure("Contains PID", str.find("PID: " + toString(session1->getPid())) != string::npos);
	}
	
	TEST_METHOD(28) {
		// Test toXml(true)
		SessionPtr session1 = spawnRackApp(pool, "stub/rack");
		string xml = pool->toXml();
		ensure("Contains <process>", xml.find("<process>") != string::npos);
		ensure("Contains PID", xml.find("<pid>" + toString(session1->getPid()) + "</pid>") != string::npos);
		ensure("Contains sensitive information", xml.find("<server_sockets>") != string::npos);
	}
	
	TEST_METHOD(29) {
		// Test toXml(false)
		SessionPtr session1 = spawnRackApp(pool, "stub/rack");
		string xml = pool->toXml(false);
		ensure("Contains <process>", xml.find("<process>") != string::npos);
		ensure("Contains PID", xml.find("<pid>" + toString(session1->getPid()) + "</pid>") != string::npos);
		ensure("Does not contain sensitive information", xml.find("<server_sockets>") == string::npos);
	}
	
	TEST_METHOD(30) {
		// Test detach().
		
		// Create 2 processes, where only the first one is active.
		SessionPtr session1 = spawnRackApp(pool, "stub/rack");
		SessionPtr session2 = spawnRackApp(pool2, "stub/rack");
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 1u && pool->getCount() == 2u;
		);
		
		// Make sure session2 refers to a different process than session1.
		session2 = spawnRackApp(pool2, "stub/rack");
		string session2dk = session2->getDetachKey();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 1u;
		);
		
		// First detach works. It was active so the 'active' property
		// is decremented.
		ensure("(10)", pool->detach(session1->getDetachKey()));
		ensure_equals("(11)", pool->getActive(), 0u);
		ensure_equals("(12)", pool->getCount(), 1u);
		
		// Second detach with the same identifier doesn't do anything.
		ensure("(20)", !pool->detach(session1->getDetachKey()));
		ensure_equals("(21)", pool->getActive(), 0u);
		ensure_equals("(22)", pool->getCount(), 1u);
		
		// Detaching an inactive process works too.
		ensure("(30)", pool->detach(session2dk));
		ensure_equals("(31)", pool->getActive(), 0u);
		ensure_equals("(32)", pool->getCount(), 0u);
	}
	
	TEST_METHOD(31) {
		// If the app group does not yet exist, and options.minProcesses > 0,
		// then get() will spawn 1 process immediately, return its session,
		// and spawn more processes in the background until options.minProcesses
		// is satisfied.
		TempDirCopy c1("stub/rack", "rackapp.tmp");
		PoolOptions options;
		options.appRoot = "rackapp.tmp";
		options.appType = "rack";
		options.minProcesses = 3;
		options.spawnMethod = "conservative";
		
		writeFile("rackapp.tmp/config.ru",
			"sleep 0.1\n"
			"run lambda {}\n");
		
		SessionPtr session1 = pool->get(options);
		ensure_equals(pool->getActive(), 1u);
		ensure_equals(pool->getCount(), 1u);
		
		EVENTUALLY(5,
			result = pool->getCount() == 3u;
		);
	}
	
	TEST_METHOD(32) {
		// If the app group already exists, all processes are active,
		// count < max, options.minProcesses > 0 and global queuing turned off,
		// then get() will check out an existing process immediately
		// and spawn new ones in the background until options.minProcesses
		// is satisfied.
		TempDirCopy c1("stub/rack", "rackapp.tmp");
		PoolOptions options;
		options.appRoot = "rackapp.tmp";
		options.appType = "rack";
		options.spawnMethod = "conservative";
		options.minProcesses = 3;
		pool->setMax(3);
		
		// Spawn a single process.
		SessionPtr session1 = pool->get(options);
		ensure_equals(pool->getActive(), 1u);
		ensure_equals(pool->getCount(), 1u);
		
		writeFile("rackapp.tmp/config.ru",
			"sleep 0.1\n"
			"run lambda {}\n");
		
		// Now call get(); this one will use the previous process
		// and spawn a new one in the background.
		SessionPtr session2 = pool2->get(options);
		ensure_equals(pool->getActive(), 1u);
		ensure_equals(pool->getCount(), 1u);
		ensure_equals(session1->getPid(), session2->getPid());
		
		EVENTUALLY(5,
			result = pool->getCount() == 3u;
		);
	}
	
	/* If the app group already exists, all processes are active,
	 * count < max, options.minProcesses > 0 and global queuing turned on,
	 * then get() will wait until either
	 * (1) an existing process becomes inactive.
	 * or until
	 * (2) a new process has been spawned.
	 */
	
	TEST_METHOD(33) {
		// Here we test scenario (1).
		PoolOptions options;
		options.appRoot = "stub/rack";
		options.appType = "rack";
		options.minProcesses = 3;
		options.useGlobalQueue = true;
		pool->setMax(3);
		
		ApplicationPool::Ptr pool3 = newPoolConnection();
		ApplicationPool::Ptr pool4 = newPoolConnection();
		
		// Spawn 3 processes.
		SessionPtr session1 = pool->get(options);
		SessionPtr session2 = pool2->get(options);
		session2.reset();
		EVENTUALLY(5,
			result = pool->getCount() == 3u;
		);
		
		// Make sure all of them are active.
		session2 = pool2->get(options);
		SessionPtr session3 = pool3->get(options);
		ensure_equals(pool->getActive(), 3u);
		ensure_equals(pool->getCount(), 3u);
		
		// Now call get() in a thread.
		SpawnRackAppFunction func;
		bool done = false;
		func.pool = pool4;
		func.done = &done;
		TempThread thr(func);
		
		usleep(20000);
		ensure("Still waiting on global queue", !done);
		ensure_equals(pool->getGlobalQueueSize(), 1u);
		
		// Make 1 process available.
		session1.reset();
		EVENTUALLY(5,
			result = done;
		);
	}
	
	TEST_METHOD(34) {
		// Here we test scenario (2).
		PoolOptions options;
		options.appRoot = "stub/rack";
		options.appType = "rack";
		options.minProcesses = 3;
		options.useGlobalQueue = true;
		pool->setMax(3);
		
		ApplicationPool::Ptr pool3 = newPoolConnection();
		ApplicationPool::Ptr pool4 = newPoolConnection();
		ApplicationPool::Ptr pool5 = newPoolConnection();
		
		// Spawn 3 processes.
		SessionPtr session1 = pool->get(options);
		SessionPtr session2 = pool2->get(options);
		session2.reset();
		EVENTUALLY(5,
			result = pool->getCount() == 3u;
		);
		
		// Make sure all of them are active.
		session2 = pool2->get(options);
		SessionPtr session3 = pool3->get(options);
		ensure_equals(pool->getActive(), 3u);
		ensure_equals(pool->getCount(), 3u);
		
		// Now call get() in a thread.
		SpawnRackAppFunction func1;
		SessionPtr session4;
		bool done1 = false;
		func1.pool = pool4;
		func1.done = &done1;
		func1.session = &session4;
		TempThread thr1(func1);
		
		// And again.
		SpawnRackAppFunction func2;
		SessionPtr session5;
		bool done2 = false;
		func2.pool = pool5;
		func2.done = &done2;
		func2.session = &session5;
		TempThread thr2(func2);
		
		// We should now arrive at a state where there are 3 processes, all
		// busy, and 2 threads waiting on the global queue.
		usleep(20000);
		ensure("Still waiting on global queue", !done1 && !done2);
		ensure_equals(pool->getGlobalQueueSize(), 2u);
		
		// Increasing the max will cause one of the threads to wake
		// up, start a spawn action in the background, and go to sleep
		// again. Eventually the new process will be done spawning,
		// causing one of the threads to wake up. The other one will
		// continue to wait.
		pool->setMax(4);
		EVENTUALLY(5,
			result = (done1 && !done2) || (!done1 && done2);
		);
	}
	
	TEST_METHOD(35) {
		// When spawning an app in the background, if it encountered an error
		// it will remove the whole app group.
		TempDirCopy c1("stub/rack", "rackapp.tmp");
		PoolOptions options;
		options.appRoot = "rackapp.tmp";
		options.appType = "rack";
		options.spawnMethod = "conservative";
		options.printExceptions = false;
		
		SessionPtr session1 = pool->get(options);
		
		writeFile("rackapp.tmp/config.ru",
			"raise 'foo'\n");
		pool2->get(options);
		
		EVENTUALLY(5,
			result = pool->getCount() == 0u;
		);
	}
	
	TEST_METHOD(36) {
		// When cleaning, at least options.minProcesses processes should be kept around.
		pool->setMaxIdleTime(0);
		ApplicationPool::Ptr pool3 = newPoolConnection();
		PoolOptions options;
		options.appRoot = "stub/rack";
		options.appType = "rack";
		options.minProcesses = 2;
		
		// Spawn 2 processes.
		SessionPtr session1 = pool->get(options);
		SessionPtr session2 = pool2->get(options);
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 0u && pool->getCount() == 2u;
		);
		
		// Spawn another process, so we get 3.
		session1 = pool->get(options);
		session2 = pool2->get(options);
		SessionPtr session3 = pool3->get(options);
		session3.reset();
		EVENTUALLY(5,
			result = pool->getActive() == 2u && pool->getCount() == 3u;
		);
		
		// Now wait until one process is idle cleaned.
		pool->setMaxIdleTime(1);
		EVENTUALLY(10,
			result = pool->getCount() == 2u;
		);
	}
	
	TEST_METHOD(37) {
		// Test whether processes are grouped together by appGroupName.
		TempDirCopy c1("stub/rack", "rackapp.tmp");
		PoolOptions options1;
		options1.appRoot = "rackapp.tmp";
		options1.appType = "rack";
		options1.appGroupName = "group A";
		SessionPtr session1 = pool->get(options1);
		
		TempDirCopy c2("stub/rack", "rackapp2.tmp");
		PoolOptions options2;
		options2.appRoot = "rackapp2.tmp";
		options2.appType = "rack";
		options2.appGroupName = "group A";
		SessionPtr session2 = pool2->get(options2);
		
		session1.reset();
		session2.reset();
		EVENTUALLY(5,
			result = pool->getCount() == 2u;
		);
		
		touchFile("rackapp.tmp/tmp/restart.txt");
		session1 = pool->get(options1);
		ensure_equals(pool->getCount(), 1u);
	}
	
	/*************************************/
	
#endif /* USE_TEMPLATE */
