text: #include <TestSupport.h>
  #include <Core/SpawningKit/Handshake/Prepare.h>
  #include <FileTools/FileManip.h>
  #include <SystemTools/UserDatabase.h>
  
  using namespace std;
  using namespace Passenger;
  using namespace Passenger::SpawningKit;
  
  namespace tut {
  	struct SpawnEnvSetupperTest: public TestBase {
  		WrapperRegistry::Registry wrapperRegistry;
  		SpawningKit::Context::Schema schema;
  		SpawningKit::Context context;
  		SpawningKit::Config config;
  		boost::shared_ptr<HandshakeSession> session;
  
  		SpawnEnvSetupperTest()
  			: context(schema)
  		{
  			wrapperRegistry.finalize();
  			context.resourceLocator = resourceLocator;
  			context.wrapperRegistry = &wrapperRegistry;
  			context.integrationMode = "standalone";
  			context.spawnDir = getSystemTempDir();
  			context.finalize();
  
  			config.startCommand = "true";
  			config.appGroupName = "appgroup";
  			config.appRoot = "tmp.wsgi";
  			config.startupFile = "tmp.wsgi/passenger_wsgi.py";
  			config.appType = "wsgi";
  			config.spawnMethod = "direct";
  			config.user = lookupSystemUsernameByUid(getuid());
  			config.group = lookupSystemGroupnameByGid(getgid());
  			config.internStrings();
  		}
  
  		void init(JourneyType type, const Json::Value &extraArgs = Json::Value()) {
  			vector<StaticString> errors;
  			ensure("Config is valid", config.validate(errors));
  			session = boost::make_shared<HandshakeSession>(context, config, type);
  
  			session->journey.setStepInProgress(SPAWNING_KIT_PREPARATION);
  			HandshakePrepare(*session, extraArgs).execute();
  
  			session->journey.setStepInProgress(SPAWNING_KIT_HANDSHAKE_PERFORM);
  			session->journey.setStepInProgress(SUBPROCESS_BEFORE_FIRST_EXEC);
  		}
  
  		bool execute(const StaticString &mode, bool quiet = false) {
  			string command = escapeShell(resourceLocator->findSupportBinary(AGENT_EXE))
  				+ " spawn-env-setupper "
  				+ escapeShell(session->workDir->getPath())
  				+ " " + mode;
  			if (quiet) {
  				command.append(" >/dev/null 2>/dev/null");
  			}
  			return runShellCommand(command) == 0;
  		}
  	};
  
  	DEFINE_TEST_GROUP(SpawnEnvSetupperTest);
  
  
  	/***** Dumping information *****/
  
  	TEST_METHOD(1) {
  		set_test_name("It sets the SUBPROCESS_BEFORE_FIRST_EXEC to the PERFORMED state");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		ensure_equals(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/steps/subprocess_before_first_exec/state"),
  			"STEP_PERFORMED");
  	}
  
  	TEST_METHOD(2) {
  		set_test_name("It dumps environment variables into the work dir");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		string envvars = unsafeReadFile(session->workDir->getPath()
  			+ "/envdump/envvars");
  		ensure(containsSubstring(envvars, "PATH="));
  	}
  
  	TEST_METHOD(3) {
  		set_test_name("It dumps user info into the work dir");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		string envvars = unsafeReadFile(session->workDir->getPath()
  			+ "/envdump/user_info");
  		ensure(containsSubstring(envvars, "uid="));
  	}
  
  	TEST_METHOD(4) {
  		set_test_name("It dumps ulimits info into the work dir");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		string envvars = unsafeReadFile(session->workDir->getPath()
  			+ "/envdump/ulimits");
  		ensure(containsSubstring(envvars, "open files")
  			|| containsSubstring(envvars, "nofiles"));
  	}
  
  	TEST_METHOD(5) {
  		set_test_name("It sets default environment variables such as PASSENGER_APP_ENV");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		string envvars = unsafeReadFile(session->workDir->getPath()
  			+ "/envdump/envvars");
  		ensure(containsSubstring(envvars, "PASSENGER_APP_ENV="));
  	}
  
  
  	/***** Command execution and environment modification *****/
  
  	TEST_METHOD(10) {
  		set_test_name("It runs the start command inside the app root");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		config.startCommand = "touch foo";
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		ensure("Start command succeeds", fileExists("tmp.wsgi/foo"));
  	}
  
  	TEST_METHOD(11) {
  		set_test_name("It sets the environment variables specified in the config");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		config.environmentVariables.insert("MY_VAR", "value");
  		config.startCommand = "echo 'import os, json; print(json.dumps(dict(os.environ)))' | python > env.json";
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		Json::Value doc;
  		ensure("Load environment JSON dump",
  			Json::Reader().parse(unsafeReadFile("tmp.wsgi/env.json"), doc));
  		ensure_equals(doc["MY_VAR"].asString(), "value");
  	}
  
  	TEST_METHOD(12) {
  		set_test_name("It switches to the corresponding user and group, if possible");
  
  		if (geteuid() != 0) {
  			return;
  		}
  
  		string user = testConfig["normal_user_1"].asString();
  		string group = testConfig["normal_group_1"].asString();
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		runShellCommand("chown -R " + user + ":" + group + " tmp.wsgi");
  		config.user = user;
  		config.group = group;
  		config.startCommand = "sh -c 'id -un > user.txt && id -gn > group.txt'";
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		ensure_equals(strip(unsafeReadFile("tmp.wsgi/user.txt")), user);
  		ensure_equals(strip(unsafeReadFile("tmp.wsgi/group.txt")), group);
  	}
  
  	TEST_METHOD(13) {
  		set_test_name("It sets ulimits to the corresponding settings, if possible");
  
  		if (geteuid() != 0) {
  			return;
  		}
  
  		string user = testConfig["normal_user_1"].asString();
  		string group = testConfig["normal_group_1"].asString();
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		runShellCommand("chown -R " + user + ":" + group + " tmp.wsgi");
  		config.fileDescriptorUlimit = 128;
  		config.startCommand = "sh -c 'ulimit -n > openfiles.txt'";
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		ensure_equals(strip(unsafeReadFile("tmp.wsgi/openfiles.txt")), "128");
  	}
  
  
  	/***** Step state recording *****/
  
  	TEST_METHOD(20) {
  		set_test_name("It sets the SUBPROCESS_SPAWN_ENV_SETUPPER_BEFORE_SHELL"
  			" and SUBPROCESS_SPAWN_ENV_SETUPPER_AFTER_SHELL steps to the PERFORMED state");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		ensure_equals(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/steps/subprocess_spawn_env_setupper_before_shell/state"),
  			"STEP_PERFORMED");
  		ensure_equals(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/steps/subprocess_spawn_env_setupper_after_shell/state"),
  			"STEP_PERFORMED");
  	}
  
  	TEST_METHOD(21) {
  		set_test_name("If loadShellEnvvars is true, it sets SUBPROCESS_OS_SHELL"
  			" step to the PERFORMED state");
  
  		// This test is known to fail erroneously if all of
  		// the following conditions apply:
  		// - You are running this test with root privileges.
  		// - The root user's shell is not supported by
  		//   the shouldLoadShellEnvvars() function in SpawnEnvSetupperMain.cpp.
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		config.loadShellEnvvars = true;
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		ensure_equals(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/steps/subprocess_os_shell/state"),
  			"STEP_PERFORMED");
  	}
  
  	TEST_METHOD(22) {
  		set_test_name("If loadShellEnvvars is false, it keeps the SUBPROCESS_OS_SHELL"
  			" step in the NOT_STARTED state");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		config.loadShellEnvvars = false;
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		ensure(!fileExists(session->workDir->getPath()
  			+ "/response/steps/subprocess_os_shell"));
  	}
  
  	TEST_METHOD(23) {
  		set_test_name("If startsUsingWrapper is true,"
  			" and the start command can be executed,"
  			" then it sets the SUBPROCESS_EXEC_WRAPPER step to the IN_PROGRESS state");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		config.startsUsingWrapper = true;
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		ensure_equals(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/steps/subprocess_exec_wrapper/state"),
  			"STEP_IN_PROGRESS");
  	}
  
  	TEST_METHOD(24) {
  		set_test_name("If startsUsingWrapper is true,"
  			" and the start command cannot be executed,"
  			" then it sets the SUBPROCESS_EXEC_WRAPPER step to the ERRORED state");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		config.startsUsingWrapper = true;
  		Json::Value extraArgs;
  		extraArgs["_bin_sh_path"] = "/non-existant-command";
  		init(SPAWN_DIRECTLY, extraArgs);
  		ensure("SpawnEnvSetupper fails", !execute("--before", true));
  
  		ensure_equals(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/steps/subprocess_exec_wrapper/state"),
  			"STEP_ERRORED");
  	}
  
  	TEST_METHOD(25) {
  		set_test_name("If startsUsingWrapper is false,"
  			" and the start command can be executed,"
  			" then it sets the SUBPROCESS_APP_LOAD_OR_EXEC step to the IN_PROGRESS state");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		config.startsUsingWrapper = false;
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper succeeds", execute("--before"));
  
  		ensure_equals(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/steps/subprocess_app_load_or_exec/state"),
  			"STEP_IN_PROGRESS");
  	}
  
  	TEST_METHOD(26) {
  		set_test_name("If startsUsingWrapper is false,"
  			" and the start command cannot be executed,"
  			" then it sets the SUBPROCESS_APP_LOAD_OR_EXEC step to the ERRORED state");
  
  		TempDirCopy dir("stub/wsgi", "tmp.wsgi");
  		config.startsUsingWrapper = false;
  		Json::Value extraArgs;
  		extraArgs["_bin_sh_path"] = "/non-existant-command";
  		init(SPAWN_DIRECTLY, extraArgs);
  		ensure("SpawnEnvSetupper fails", !execute("--before", true));
  
  		ensure_equals(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/steps/subprocess_app_load_or_exec/state"),
  			"STEP_ERRORED");
  	}
  
  
  	/***** Miscellaneous *****/
  
  	TEST_METHOD(30) {
  		set_test_name("If the user does not have a access to one of the app root's"
  			" parent directories, or the app root itself, then it provides a clear"
  			" error message explaining this (level 1)");
  
  		if (geteuid() == 0) {
  			return;
  		}
  
  		runShellCommand("mkdir -p tmp.check/a/b/c");
  		TempDirCopy dir("stub/wsgi", "tmp.check/a/b/c/d");
  		TempDir dir2("tmp.check");
  		runShellCommand("chmod 000 tmp.check/a/b/c/d");
  		runShellCommand("chmod 600 tmp.check/a/b/c");
  		runShellCommand("chmod 600 tmp.check/a");
  
  		char buffer[PATH_MAX];
  		string cwd = getcwd(buffer, sizeof(buffer));
  
  		if (defaultLogLevel == (LoggingKit::Level) DEFAULT_LOG_LEVEL) {
  			// If the user did not customize the test's log level,
  			// then we'll want to tone down the noise.
  			LoggingKit::setLevel(LoggingKit::CRIT);
  		}
  
  		config.appRoot = "tmp.check/a/b/c/d";
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper fails", !execute("--before", true));
  
  		ensure(containsSubstring(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/error/summary"),
  			"Directory '" + cwd + "/tmp.check/a' is inaccessible"));
  
  		#if 0
  		try {
  			spawner->spawn(options);
  			fail("SpawnException expected");
  		} catch (const SpawnException &e) {
  			ensure("(1)", containsSubstring(e.getErrorPage(),
  				"the parent directory '" + cwd + "/tmp.check/a' has wrong permissions"));
  		}
  
  		runShellCommand("chmod 700 tmp.check/a");
  		try {
  			spawner->spawn(options);
  			fail("SpawnException expected");
  		} catch (const SpawnException &e) {
  			ensure("(2)", containsSubstring(e.getErrorPage(),
  				"the parent directory '" + cwd + "/tmp.check/a/b/c' has wrong permissions"));
  		}
  
  		runShellCommand("chmod 700 tmp.check/a/b/c");
  		try {
  			spawner->spawn(options);
  			fail("SpawnException expected");
  		} catch (const SpawnException &e) {
  			ensure("(3)", containsSubstring(e.getErrorPage(),
  				"However this directory is not accessible because it has wrong permissions."));
  		}
  
  		runShellCommand("chmod 700 tmp.check/a/b/c/d");
  		spawner->spawn(options); // Should not throw.
  		#endif
  	}
  
  	TEST_METHOD(31) {
  		set_test_name("If the user does not have a access to one of the app root's"
  			" parent directories, or the app root itself, then it provides a clear"
  			" error message explaining this (level 2)");
  
  		if (geteuid() == 0) {
  			return;
  		}
  
  		runShellCommand("mkdir -p tmp.check/a/b/c");
  		TempDirCopy dir("stub/wsgi", "tmp.check/a/b/c/d");
  		TempDir dir2("tmp.check");
  		runShellCommand("chmod 000 tmp.check/a/b/c/d");
  		runShellCommand("chmod 600 tmp.check/a/b/c");
  		runShellCommand("chmod 700 tmp.check/a");
  
  		char buffer[PATH_MAX];
  		string cwd = getcwd(buffer, sizeof(buffer));
  
  		if (defaultLogLevel == (LoggingKit::Level) DEFAULT_LOG_LEVEL) {
  			// If the user did not customize the test's log level,
  			// then we'll want to tone down the noise.
  			LoggingKit::setLevel(LoggingKit::CRIT);
  		}
  
  		config.appRoot = "tmp.check/a/b/c/d";
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper fails", !execute("--before", true));
  
  		ensure(containsSubstring(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/error/summary"),
  			"Directory '" + cwd + "/tmp.check/a/b/c' is inaccessible"));
  	}
  
  	TEST_METHOD(32) {
  		set_test_name("If the user does not have a access to one of the app root's"
  			" parent directories, or the app root itself, then it provides a clear"
  			" error message explaining this (level 3)");
  
  		if (geteuid() == 0) {
  			return;
  		}
  
  		runShellCommand("mkdir -p tmp.check/a/b/c");
  		TempDirCopy dir("stub/wsgi", "tmp.check/a/b/c/d");
  		TempDir dir2("tmp.check");
  		runShellCommand("chmod 700 tmp.check/a/b/c/d");
  		runShellCommand("chmod 600 tmp.check/a/b/c");
  		runShellCommand("chmod 700 tmp.check/a");
  
  		char buffer[PATH_MAX];
  		string cwd = getcwd(buffer, sizeof(buffer));
  
  		if (defaultLogLevel == (LoggingKit::Level) DEFAULT_LOG_LEVEL) {
  			// If the user did not customize the test's log level,
  			// then we'll want to tone down the noise.
  			LoggingKit::setLevel(LoggingKit::CRIT);
  		}
  
  		config.appRoot = "tmp.check/a/b/c/d";
  		init(SPAWN_DIRECTLY);
  		ensure("SpawnEnvSetupper fails", !execute("--before", true));
  
  		ensure(containsSubstring(
  			unsafeReadFile(session->workDir->getPath()
  				+ "/response/error/summary"),
  			"Directory '" + cwd + "/tmp.check/a/b/c' is inaccessible"));
  	}
  }
  