From 736f997ba8827f2c9a374b53e075835ed609889e Mon Sep 17 00:00:00 2001 From: Herman Venter Date: Fri, 19 Jul 2013 17:22:25 -0700 Subject: [PATCH] Use PHP test framework for testing hphpd in sandbox server mode Debugger tests that run hphp in non local mode (i.e. with the "-h some-server:some-port" option specified) currently use a clunky specialized C++ test harness that invokes PHP scripts that talk to the debugger client via the obsolete client API. This revision moves those tests to the new PHP only framework. The debugger client is now controlled by piping in commands, just like the other debugger tests. The main difference is that the debugger client is connected to a server instance and a separate PHP driver is used to load the server, load the client, and then load pages from the server so that the client can hit breakpoints. It also checks that the client can respond to ctrl-c by actually sending a SIGINT signal to the client process. --- hphp/runtime/base/program_functions.cpp | 3 +- hphp/runtime/debugger/debugger_client.cpp | 17 +- hphp/runtime/debugger/debugger_client.h | 3 +- hphp/test/quick/debugger/hphpd.hdf | 62 ---- hphp/test/run | 1 + .../debugger/config/debugger-client.hdf | 45 +++ .../debugger/config/debugger-server.hdf | 34 +++ hphp/test/server/debugger/config/hphpd.hdf | 5 + hphp/test/server/debugger/debugger/break1.php | 33 +++ hphp/test/server/debugger/debugger/hello.php | 1 + .../debugger/debugger/hphpd_startup.php | 1 + .../server/debugger/debugger/sandbox_conf | 4 + hphp/test/server/debugger/debugger/test1.in | 25 ++ hphp/test/server/debugger/debugger/test1.php | 23 ++ hphp/test/server/debugger/tests/runTest1.php | 23 ++ .../debugger/tests/runTest1.php.expectf | 94 ++++++ hphp/test/server/debugger/tests/test_base.php | 267 ++++++++++++++++++ .../debugger/tests/test_base.php.skipif | 3 + 18 files changed, 578 insertions(+), 66 deletions(-) create mode 100644 hphp/test/server/debugger/config/debugger-client.hdf create mode 100644 hphp/test/server/debugger/config/debugger-server.hdf create mode 100644 hphp/test/server/debugger/config/hphpd.hdf create mode 100644 hphp/test/server/debugger/debugger/break1.php create mode 100644 hphp/test/server/debugger/debugger/hello.php create mode 100644 hphp/test/server/debugger/debugger/hphpd_startup.php create mode 100644 hphp/test/server/debugger/debugger/sandbox_conf create mode 100644 hphp/test/server/debugger/debugger/test1.in create mode 100644 hphp/test/server/debugger/debugger/test1.php create mode 100644 hphp/test/server/debugger/tests/runTest1.php create mode 100644 hphp/test/server/debugger/tests/runTest1.php.expectf create mode 100644 hphp/test/server/debugger/tests/test_base.php create mode 100644 hphp/test/server/debugger/tests/test_base.php.skipif diff --git a/hphp/runtime/base/program_functions.cpp b/hphp/runtime/base/program_functions.cpp index f0074283a..0d80b3b0e 100644 --- a/hphp/runtime/base/program_functions.cpp +++ b/hphp/runtime/base/program_functions.cpp @@ -1141,6 +1141,7 @@ static int execute_program_impl(int argc, char** argv) { StackTraceNoHeap::AddExtraLogging("IsDebugger", "True"); RuntimeOption::EnableDebugger = true; po.debugger_options.fileName = file; + po.debugger_options.user = po.user; Eval::DebuggerProxyPtr localProxy = Eval::Debugger::StartClient(po.debugger_options); if (!localProxy) { @@ -1456,7 +1457,7 @@ bool hphp_invoke(ExecutionContext *context, const std::string &cmd, funcRet->assignVal(invoke(cmd.c_str(), funcParams)); } else { if (isServer) hphp_chdir_file(cmd); - include_impl_invoke(cmd.c_str(), once); + include_impl_invoke(cmd.c_str(), once); } } catch (...) { handle_invoke_exception(ret, context, errorMsg, error, richErrorMsg); diff --git a/hphp/runtime/debugger/debugger_client.cpp b/hphp/runtime/debugger/debugger_client.cpp index 13eda998e..0720cbfda 100644 --- a/hphp/runtime/debugger/debugger_client.cpp +++ b/hphp/runtime/debugger/debugger_client.cpp @@ -118,6 +118,9 @@ void DebuggerClient::onSignal(int sig) { int DebuggerClient::pollSignal() { TRACE(2, "DebuggerClient::pollSignal\n"); + if (m_scriptMode) { + print(".....Debugger client still waiting for server response....."); + } int ret = m_signum; m_signum = CmdSignal::SignalNone; return ret; @@ -397,7 +400,7 @@ String DebuggerClient::FormatTitle(const char *title) { /////////////////////////////////////////////////////////////////////////////// DebuggerClient::DebuggerClient(std::string name /* = "" */) - : m_tutorial(0), m_printFunction(""), + : m_tutorial(0), m_printFunction(""), m_scriptMode(false), m_logFile(""), m_logFileHandler(nullptr), m_mainThread(this, &DebuggerClient::run), m_stopped(false), m_inputState(TakingCommand), @@ -608,13 +611,19 @@ void DebuggerClient::init(const DebuggerClientOptions &options) { if (!options.configFName.empty()) { m_configFileName = options.configFName; } - m_options.user = Process::GetCurrentUser(); + if (options.user.empty()) + m_options.user = Process::GetCurrentUser(); } usageLogEvent("init"); loadConfig(); + if (m_scriptMode) { + print("running in script mode, pid=%d\n", + (int32_t)Process::GetProcessId()); + } + if (!options.cmds.empty()) { RuntimeOption::EnableDebuggerColor = false; RuntimeOption::EnableDebuggerPrompt = false; @@ -1122,6 +1131,9 @@ DebuggerCommandPtr DebuggerClient::eventLoop(EventLoopKind loopKind, console(); // Prompt loop m_inputState = TakingInterrupt; m_machine->m_interrupting = false; // Machine is running again. + if (m_scriptMode) { + print("Waiting for server response"); + } } } throw DebuggerClientExitException(); // Stopped, so exit. @@ -2347,6 +2359,7 @@ void DebuggerClient::loadConfig() { } m_tutorial = m_config["Tutorial"].getInt32(0); + m_scriptMode = m_config["ScriptMode"].getBool(); std::string pprint = m_config["PrettyPrint"].getString("hphpd_print_value"); setDebuggerBypassCheck(m_config["BypassAccessCheck"].getBool()); setDebuggerClientSmallStep(m_config["SmallStep"].getBool()); diff --git a/hphp/runtime/debugger/debugger_client.h b/hphp/runtime/debugger/debugger_client.h index 48b529e77..53115d1ad 100644 --- a/hphp/runtime/debugger/debugger_client.h +++ b/hphp/runtime/debugger/debugger_client.h @@ -365,6 +365,7 @@ private: int m_tutorial; std::string m_printFunction; std::set m_tutorialVisited; + bool m_scriptMode; // Is this client being scripted by a test? DECLARE_DBG_SETTING DECLARE_DBG_CLIENT_SETTING @@ -378,7 +379,7 @@ private: InputState m_inputState; int m_signum; // Set when ctrl-c is pressed, used by signal polling - int m_sigTime; // The last time ctrl-c was recgonized + int m_sigTime; // The last time ctrl-c was recognized // auto-completion states int m_acLen; diff --git a/hphp/test/quick/debugger/hphpd.hdf b/hphp/test/quick/debugger/hphpd.hdf index 61ca22950..20674ebe2 100644 --- a/hphp/test/quick/debugger/hphpd.hdf +++ b/hphp/test/quick/debugger/hphpd.hdf @@ -1,66 +1,4 @@ UTF8 = 1 Color = 0 -Color { - SupportedNames { - 1 = black - 2 = red - 3 = green - 4 = brown - 5 = blue - 6 = magenta - 7 = cyan - 8 = gray - 9 = dark_gray - 10 = light_red - 11 = light_green - 12 = yellow - 13 = light_blue - 14 = light_magenta - 15 = light_cyan - 16 = white - } - Palette { - emacs { - Keyword = CYAN - Comment = RED - String = GREEN - Variable = BROWN - Html = GRAY - Tag = MAGENTA - Declaration = BLUE - Constant = MAGENTA - LineNo = GRAY - } - vim { - Keyword = MAGENTA - Comment = BLUE - String = RED - Variable = CYAN - Html = GRAY - Tag = MAGENTA - Declaration = WHITE - Constant = WHITE - LineNo = GRAY - } - } - Help = BROWN - Info = GREEN - Output = CYAN - Error = RED - ItemName = GRAY - HighlightForeground = RED - HighlightBackground = GRAY - Code { - Keyword = CYAN - Comment = RED - String = GREEN - Variable = BROWN - Html = GRAY - Tag = MAGENTA - Declaration = BLUE - Constant = MAGENTA - LineNo = GRAY - } -} Tutorial = -1 MaxCodeLines = -1 diff --git a/hphp/test/run b/hphp/test/run index 4f2035aa6..d5c5ab130 100755 --- a/hphp/test/run +++ b/hphp/test/run @@ -163,6 +163,7 @@ function map_convenience_filename($file) { $mappage = array( 'quick' => 'hphp/test/quick', 'slow' => 'hphp/test/slow', + 'debugger' => 'hphp/test/server/debugger/tests', 'zend' => 'hphp/test/zend/good', 'zend_bad' => 'hphp/test/zend/bad', 'facebook' => 'hphp/facebook/test', diff --git a/hphp/test/server/debugger/config/debugger-client.hdf b/hphp/test/server/debugger/config/debugger-client.hdf new file mode 100644 index 000000000..740363329 --- /dev/null +++ b/hphp/test/server/debugger/config/debugger-client.hdf @@ -0,0 +1,45 @@ +Eval { + EnableXHP = true + AllowHhas = true + EnableHipHopSyntax = true + EnableObjDestructCall = true + JitASize = 10485760 # 10 MB + JitAStubsSize = 10485760 # 10 MB + JitGlobalDataSize = 2097152 # 2 MB + Debugger { + EnableDebugger = true + EnableDebuggerColor = false + EnableDebuggerPrompt = false + } +} + +Repo { + Local.Mode = -- + Eval.Mode = readonly + Commit = true + DebugInfo = true +} + +MySQL { + ReadTimeout = 5000 +} + +EnvVariables { + HPHP_INTERPRETER = 1 +} + +ServerVariables { + ALPHA_CONSOLE = 1 + TFBENV = 16777216 +} + +ErrorHandling { + NoticeFrequency = 1 + WarningFrequency = 1 +} + +ResourceLimit { + SerializationSizeLimit=134217728 +} + + diff --git a/hphp/test/server/debugger/config/debugger-server.hdf b/hphp/test/server/debugger/config/debugger-server.hdf new file mode 100644 index 000000000..700613283 --- /dev/null +++ b/hphp/test/server/debugger/config/debugger-server.hdf @@ -0,0 +1,34 @@ + +Log { + AlwaysLogUnhandledExceptions = false +} + +Server { + AllowedFiles { + * = hello.php + * = hphpd.php + } +} + +Eval { + Debugger { + EnableDebugger = true + EnableDebuggerServer = true + StartupDocument = hphpd_startup.php + SignalTimeout = 5 + } +} + +Sandbox { + SandboxMode = true + ConfFile = sandbox_conf + # only match hphpd..sth.sth, not any requests + Pattern = hphpd\.([a-z0-9_-]+)\.[a-z0-9_-]+\.[a-z0-9_-]+ +} + +Repo { + Local.Mode = -- + Eval.Mode = readonly + Commit = true + DebugInfo = true +} diff --git a/hphp/test/server/debugger/config/hphpd.hdf b/hphp/test/server/debugger/config/hphpd.hdf new file mode 100644 index 000000000..32231590b --- /dev/null +++ b/hphp/test/server/debugger/config/hphpd.hdf @@ -0,0 +1,5 @@ +UTF8 = 1 +Color = 0 +ScriptMode = true +Tutorial = -1 +MaxCodeLines = -1 diff --git a/hphp/test/server/debugger/debugger/break1.php b/hphp/test/server/debugger/debugger/break1.php new file mode 100644 index 000000000..5ff8b0e34 --- /dev/null +++ b/hphp/test/server/debugger/debugger/break1.php @@ -0,0 +1,33 @@ +pubObj($x); + } + public function callCallPubObj($x) { + $this->callPubObj($x); + } +} + +error_log('break1.php loaded'); diff --git a/hphp/test/server/debugger/debugger/hello.php b/hphp/test/server/debugger/debugger/hello.php new file mode 100644 index 000000000..790c7dc25 --- /dev/null +++ b/hphp/test/server/debugger/debugger/hello.php @@ -0,0 +1 @@ +pubObj($x); + cls::pubCls($x); + $obj->pubHardBreak($x); +} + +function test_sleep() { + $a = 1; + // $a will be set to 0 by debugger after interrupt + while ($a == 1) { sleep(1); } + return $a; +} + +test_break(); +test_sleep(); +echo "request done\n"; diff --git a/hphp/test/server/debugger/tests/runTest1.php b/hphp/test/server/debugger/tests/runTest1.php new file mode 100644 index 000000000..c38a6ac74 --- /dev/null +++ b/hphp/test/server/debugger/tests/runTest1.php @@ -0,0 +1,23 @@ + /tmp/hphpd_test_server_stdout$test_run_id.log" . + " 2> /tmp/hphpd_test_server_stderr$test_run_id.log"; + + tlog('Starting server with command: '.$cmd); + $pipes = array(); + $serverProc = proc_open($cmd, array(), $pipes); + if (!is_resource($serverProc)) { + tlog('Failed to start a shell process for the server'); + dumpLogFilesToStdoutAndDie(); + } + return $serverProc; +} + +function waitForServerToGetGoing($serverPort) { + $url = "http://".php_uname('n').':'.$serverPort.'/hello.php'; + $r = ""; + for ($i = 1; $i <= 20; $i++) { + sleep(1); + $r = request($url); + if ($r === "Hello, World!") break; + } + + if ($r !== "Hello, World!") { + tlog('Server is not responding.'); + dumpLogFilesToStdoutAndDie(); + } +} + +function stopServer($adminPort) { + global $test_run_id; + + $url = "http://".php_uname('n').':'.$adminPort.'/stop'; + $r = ""; + for ($i = 1; $i <= 10; $i++) { + $r = request($url); + if ($r == "OK\n") break; + usleep(100000); + } + if ($r != "OK\n") { + tlog("Server did not stop."); + dumpLogFilesToStdoutAndDie(); + } + unlink("/tmp/hphpd_test$test_run_id.log"); + unlink("/tmp/hphpd_test_server$test_run_id.log"); + unlink("/tmp/hphpd_test_server_stderr$test_run_id.log"); + unlink("/tmp/hphpd_test_server_stdout$test_run_id.log"); + unlink("/tmp/hphpd_test_client$test_run_id.log"); + unlink("/tmp/hphpd_server$test_run_id.hhbc"); + unlink("/tmp/hphpd_client$test_run_id.hhbc"); +} + +function request($url, $timeout = 1200) { + tlog("Fetching: $url"); + $ch = curl_init(); + $host = "hphpd.debugger.".php_uname('n'); + curl_setopt($ch, CURLOPT_HTTPHEADER, array("Host: $host")); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); + $result = curl_exec($ch); + curl_close($ch); + return $result; +} + +function startDebuggerClient($debugPort, $input_path, &$pipes) { + global $test_run_id; + + $home = hphp_home().'/hphp/test/server/debugger'; + $hhvm = bin_root().'/hphp/hhvm/hhvm'; + $host = ' -h '.php_uname('n'); + $port = ' --debug-port '.$debugPort; + $user = ' --user debugger'; + $config = ' --config '.$home.'/config/debugger-client.hdf'; + $logFileConfig = ' -vLog.File='."/tmp/hphpd_test_client$test_run_id.log"; + $repoConfig = " -vRepo.Central.Path=/tmp/hphpd_client$test_run_id.hhbc"; + $debugConfig = ' --debug-config '.$home.'/config/hphpd.hdf'; + + $cmd = $hhvm.' -m debug' . $host . $port . $user . + $config . $logFileConfig . $repoConfig . $debugConfig . + ' <'.$home.$input_path; + + $descriptorspec = array( + 0 => array("pipe", "r"), + 1 => array("pipe", "w"), + 2 => array("pipe", "w"), + ); + + $env = $_ENV; + $env["TERM"] = "dumb"; + + tlog('Starting debugger client with command: '.$cmd); + $process = proc_open("$cmd 2>&1", $descriptorspec, $pipes, null, $env); + if (!is_resource($process)) { + tlog('Failed to start a shell process for the server'); + dumpLogFilesToStdoutAndDie(); + } +} + +function runTest($testName, $testController) { + try { + $serverPort = get_random_port(); + $adminPort = get_random_port(); + while ($adminPort === $serverPort) { + $adminPort = get_random_port(); + } + $debugPort = get_random_port(); + while ($debugPort === $serverPort || $debugPort === $adminPort) { + $debugPort = get_random_port(); + } + + $pid = posix_getpid(); + $serverProc = null; + $clientProcessId = 0; + $serverProc = startServer($serverPort, $adminPort, $debugPort); + waitForServerToGetGoing($serverPort); + startDebuggerClient($debugPort, "/debugger/$testName.in", $pipes); + $clientProcessId = getClientProcessId($pipes[1]); + if (!$clientProcessId || + ($clientProcessId = intval($clientProcessId)) <= 0) { + tlog('Failed to communicate with the debugger client process'); + dumpLogFilesToStdoutAndDie(); + } + tlog("Debugger client process id = $clientProcessId"); + $testController($pipes[1], $clientProcessId, $serverPort); + // Echo stderr, just in case. + // (It was redirected to stdout, so this should be empty). + echo stream_get_contents($pipes[2]); + stopServer($adminPort); + } catch (Exception $e) { + error_log("Caught exception, test failed, pid=$pid"); + killChildren(posix_getpid()); + error_log('test failed'); + } +} + +function killChildren($pid) { + $childIds = exec("pgrep -f -d , -P $pid"); + foreach (explode(",", $childIds) as $cid) { + if (!$cid) continue; + killChildren($cid); + error_log("killing $cid"); + posix_kill($cid, SIGKILL); + } +} + +function getClientProcessId($pipe) { + tlog("reading initial client output for client process id"); + while (!feof($pipe)) { + $clientOutput = fgets($pipe); + tlog($clientOutput); + if (strpos($clientOutput, "running in script mode, pid=") === 0) { + return substr($clientOutput, 28); + } + } + if (feof($pipe)) tlog("client closed the pipe."); + tlog("done reading client output for client process id"); +} + +function waitForClientToOutput($pipe, $string1, $retryCount = 20) { + tlog("reading client output"); + $rc = $retryCount; + while (!feof($pipe)) { + $clientOutput = fgets($pipe); + tlog($clientOutput); + if (strpos($clientOutput, + ".....Debugger client still waiting for server response.....") === 0) { + if (--$rc > 0) continue; + dumpLogFilesToStdoutAndDie(); + } + echo $clientOutput; + if (strpos($clientOutput, $string1) === 0) break; + $rc = $retryCount; + } + if (feof($pipe)) tlog("client closed the pipe."); + tlog("done reading client output"); +} + diff --git a/hphp/test/server/debugger/tests/test_base.php.skipif b/hphp/test/server/debugger/tests/test_base.php.skipif new file mode 100644 index 000000000..d620e2299 --- /dev/null +++ b/hphp/test/server/debugger/tests/test_base.php.skipif @@ -0,0 +1,3 @@ +