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 @@ +