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.
Esse commit está contido em:
Herman Venter
2013-07-19 17:22:25 -07:00
commit de Sara Golemon
commit 736f997ba8
18 arquivos alterados com 578 adições e 66 exclusões
+2 -1
Ver Arquivo
@@ -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);
+15 -2
Ver Arquivo
@@ -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());
+2 -1
Ver Arquivo
@@ -365,6 +365,7 @@ private:
int m_tutorial;
std::string m_printFunction;
std::set<std::string> 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;
-62
Ver Arquivo
@@ -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
+1
Ver Arquivo
@@ -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',
@@ -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
}
@@ -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.<user>.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
}
+5
Ver Arquivo
@@ -0,0 +1,5 @@
UTF8 = 1
Color = 0
ScriptMode = true
Tutorial = -1
MaxCodeLines = -1
@@ -0,0 +1,33 @@
<?php
// Warning: line numbers are sensitive, do not change
function foo($x) {
$y = $x.'_suffix';
error_log($y);
}
class cls {
public function pubObj($x) {
error_log("pubObj:".$x);
}
public static function pubCls($x) {
error_log("pubCls:".$x);
}
public function pubHardBreak($x) {
error_log("pubHardBreak:".$x);
hphpd_break();
error_log("pubHardBreak:done");
}
}
class derived extends cls {
public function callPubObj($x) {
$this->pubObj($x);
}
public function callCallPubObj($x) {
$this->callPubObj($x);
}
}
error_log('break1.php loaded');
@@ -0,0 +1 @@
<?php echo "Hello, World!";
@@ -0,0 +1 @@
<?php
@@ -0,0 +1,4 @@
default.path = .
default.accesslog = /tmp/hphpd_test_sandbox_access.log
default.ServerVars {
}
+25
Ver Arquivo
@@ -0,0 +1,25 @@
break break1.php:7
continue
variable
break clear all
break foo()
continue
break clear all
break cls::pubObj()
continue
break clear all
break cls::pubCls()
continue
break clear all
continue
continue
@ $a = 0
break test1.php:18
continue
p $a
break clear all
break end test1.php
continue
break psp test1.php
continue
quit
+23
Ver Arquivo
@@ -0,0 +1,23 @@
<?php
include 'break1.php';
function test_break() {
$x = 'test_break() in test1.php';
foo($x);
foo($x);
$obj = new cls();
$obj->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";
+23
Ver Arquivo
@@ -0,0 +1,23 @@
<?php
require_once('test_base.php');
function test1Controller($hphpdOutput, $hphpdProcessId, $serverPort) {
// Request a page so that the client can debug it.
waitForClientToOutput($hphpdOutput, "Waiting for server response");
$url = "http://".php_uname('n').':'.$serverPort.'/test1.php';
echo "Requesting test1.php\n";
request($url, 1); // proceed without waiting for a response
// Send ctrl-c to client, which is waiting for a hung server
waitForClientToOutput($hphpdOutput, "Break at cls::pubHardBreak()");
waitForClientToOutput($hphpdOutput, "Waiting for server response");
sleep(2); // give server a chance to get itself in the enless loop
echo "sending SIGINT to the debugger client.\n";
posix_kill($hphpdProcessId, SIGINT);
// Let client run until script quits
waitForClientToOutput($hphpdOutput, "quit");
}
runTest('test1', "test1Controller");
@@ -0,0 +1,94 @@
Connecting to %s
Attaching to debugger's default sandbox and pre-loading, please wait...
break break1.php:7
Breakpoint 1 set on line 7 of break1.php
But wont break until file break1.php has been loaded.
continue
Waiting for server response
Requesting test1.php
Breakpoint 1 reached at foo() on line 7 of %s/break1.php
6 $y = $x.'_suffix';
7 error_log($y);
8 }
variable
$x = "test_break() in test1.php"
$y = "test_break() in test1.php_suffix"
break clear all
All breakpoints are cleared.
break foo()
Breakpoint 1 set upon entering foo()
continue
Waiting for server response
Breakpoint 1 reached at foo() on line 6 of %s/break1.php
5 function foo($x) {
6 $y = $x.'_suffix';
7 error_log($y);
break clear all
All breakpoints are cleared.
break cls::pubObj()
Breakpoint 1 set upon entering cls::pubObj()
continue
Waiting for server response
Breakpoint 1 reached at cls::pubObj() on line 12 of %s/break1.php
11 public function pubObj($x) {
12 error_log("pubObj:".$x);
13 }
break clear all
All breakpoints are cleared.
break cls::pubCls()
Breakpoint 1 set upon entering cls::pubCls()
continue
Waiting for server response
Breakpoint 1 reached at cls::pubCls() on line 15 of %s/break1.php
14 public static function pubCls($x) {
15 error_log("pubCls:".$x);
16 }
break clear all
All breakpoints are cleared.
continue
Waiting for server response
Break at cls::pubHardBreak() on line 19 of %s/break1.php
18 error_log("pubHardBreak:".$x);
19 hphpd_break();
20 error_log("pubHardBreak:done");
continue
Waiting for server response
sending SIGINT to the debugger client.
Pausing program execution, please wait...
Break at test_sleep() on line 17 of %s/test1.php
16 // $a will be set to 0 by debugger after interrupt
17 while ($a == 1) { sleep(1); }
18 return $a;
@ $a = 0
break test1.php:18
Breakpoint 1 set on line 18 of test1.php
continue
Waiting for server response
Breakpoint 1 reached at test_sleep() on line 18 of %s/test1.php
17 while ($a == 1) { sleep(1); }
18 return $a;
19 }
p $a
0
break clear all
All breakpoints are cleared.
break end test1.php
Breakpoint 1 set end of request or start of psp when request is test1.php
continue
Waiting for server response
Web request /test1.php ended.
break psp test1.php
Breakpoint 2 set end of psp when request is test1.php
continue
Waiting for server response
Post-Send Processing for /test1.php was ended.
quit
+267
Ver Arquivo
@@ -0,0 +1,267 @@
<?php
$test_run_id = rand(100000, 999999);
$error_log_file = fopen("/tmp/hphpd_test$test_run_id.log", 'w');
function tlog($str) {
global $error_log_file;
fwrite($error_log_file, $str);
fwrite($error_log_file, "\n");
// error_log($str);
}
function dumpLogFilesToStdoutAndDie() {
global $error_log_file;
global $test_run_id;
sleep(1);
error_log('-------------------------------------------');
error_log("Contents of '/tmp/hphpd_test$test_run_id.log'");
readfile("/tmp/hphpd_test$test_run_id.log");
echo "\n";
error_log('-------------------------------------------');
error_log("Contents of '/tmp/hphpd_test_server$test_run_id.log'");
readfile("/tmp/hphpd_test_server$test_run_id.log");
echo "\n";
error_log('-------------------------------------------');
error_log("Contents of '/tmp/hphpd_test_server_stdout$test_run_id.log'");
readfile("/tmp/hphpd_test_server_stdout$test_run_id.log");
echo "\n";
error_log('-------------------------------------------');
error_log("Contents of '/tmp/hphpd_test_server_stderr$test_run_id.log'");
readfile("/tmp/hphpd_test_server_stderr$test_run_id.log");
echo "\n";
error_log('-------------------------------------------');
error_log("Contents of '/tmp/hphpd_test_client$test_run_id.log'");
readfile("/tmp/hphpd_test_client$test_run_id.log");
echo "\n";
error_log('-------------------------------------------');
error_log("Contents of '/tmp/hphpd_test_sandbox_access.log'");
readfile("/tmp/hphpd_test_sandbox_access.log");
echo "\n";
error_log('-------------------------------------------');
throw new Exception("test failed");
}
function hphp_home() {
// __DIR__ == result.'hphp/test/server/debugger/tests'
return realpath(__DIR__.'/../../../../..');
}
function bin_root() {
$dir = hphp_home() . '/' . '_bin';
return is_dir($dir) ?
$dir : # fbmake
hphp_home() # github
;
}
function get_random_port() {
$BasePort = 20000;
$PortRange = 3000;
return rand($BasePort, $BasePort+$PortRange);
}
function startServer($serverPort, $adminPort, $debugPort) {
global $test_run_id;
$home = hphp_home().'/hphp/test/server/debugger';
$portConfig = ' -vServer.Port='.$serverPort;
$serverConfig = ' --config='.$home.'/config/debugger-server.hdf';
$logFileConfig = ' -vLog.File='."/tmp/hphpd_test_server$test_run_id.log";
$srcRootConfig = ' -vServer.SourceRoot='.$home.'/debugger';
$includePathConfig = ' -vServer.IncludeSearchPaths.0='.$home.'/debugger';
$adminPortConfig = ' -vAdminServer.Port='.$adminPort;
$debugPortConfig = ' -vEval.Debugger.Port='.$debugPort;
$repoConfig = " -vRepo.Central.Path=/tmp/hphpd_server$test_run_id.hhbc";
$useJit = array_key_exists('HHVM_JIT', $_ENV) && $_ENV['HHVM_JIT'] == 1;
$jitConfig = ' -vEval.Jit='.($useJit ? "true" : "false");
// To emulate sandbox setup, let Sandbox.Home be '$home'
// and user name be 'debugger', so that the server can find the sandbox_conf
// in '$home'.'/debugger'.
$sandboxHomeConfig = ' -vSandbox.Home='.$home;
$hhvm = bin_root().'/hphp/hhvm/hhvm';
$cmd = $hhvm.' --mode=server' . $serverConfig . $logFileConfig .
' -vEval.JitWarmupRequests=0' . $portConfig . $srcRootConfig .
$includePathConfig . $sandboxHomeConfig . $adminPortConfig .
$debugPortConfig . $repoConfig . $jitConfig .
" > /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");
}
@@ -0,0 +1,3 @@
<?php
echo 'skip';