Location: PHPKode > projects > phc > test/framework/lib/header.php
<?php
/*
 * phc -- the open source PHP compiler
 * See doc/license/README.license for licensing information
 *
 * Some standard functions
 */

function get_phc ()
{
	global $opt_valgrind, $phc_suffix, $valgrind, $libphp, $working_dir;

	// first check that this isnt being run from the wrong directory
	// (the right directory contains ./phc - if you get this far you
	// have the tests)
	$phc = "src/phc$phc_suffix";
	if (!(file_exists($phc) and is_file($phc) and is_executable($phc)))
	{
		$cwd = getcwd ();
		die ("Error: The current directory, $cwd, does not contain the phc executable '$phc'\n");
	}


	// now copy to the working directory, so we can safely compile
	// the executable while the tests run.
	$phc = copy_to_working_dir ($phc);

	if ($opt_valgrind)
	{
		if (!$valgrind) die ("Error: Valgrind not available (see output from ./configure)\n");
		# we turn off the leak check because we dont tidy up any garbage
		$phc = "$valgrind -q --suppressions=misc/valgrind_suppressions --leak-check=no $phc";
	}
	return $phc;

}

function get_phc_compile_plugin ()
{
	// first check that this isnt being run from the wrong directory (the right directory contains ./phc - if you get this far you have the tests)
	$phc_compile_plugin = "src/phc_compile_plugin";
	if (!file_exists($phc_compile_plugin) and is_file($phc_compile_plugin) and is_executable($phc_compile_plugin))
	{
		$cwd = getcwd ();
		die ("Error: The current directory, $cwd, does not contain the phc executable '$phc'\n");
	}

	$phc_compile_plugin = copy_to_working_dir ($phc_compile_plugin);

	return $phc_compile_plugin;

}

function get_php ()
{
	global $php_exe;

	# use the version of PHP found by configure
	if ($php_exe)
	{
		# remember this screipt si running with the same php we will be using
		if (PHP_SAPI == "cgi")
		{
			return "$php_exe -Cq -d html_errors=0";
		}

		return "$php_exe -C";
	}
	else
	{
		$php = trim (`which php`);
		return "$php -C";
	}
}

// For a pipe, just put a blank $subject
function get_php_command_line ($subject, $pipe = false)
{
	phc_assert ($subject != "", "dont pass empty subject, even for pipe");

	global $php;
	global $opt_long;

	if ($opt_long)
		$max_exe = 12;
	else
		$max_exe = 5;

	$dir_path = dirname($subject);
	if ($pipe) $subject = ""; # we need the subject for the dir_name
	return "$php -d include_path=./:test/subjects/:$dir_path -d max_execution_time=$max_exe $subject ";
}

function get_phc_command_line ($subject)
{
	phc_assert ($subject != "", "dont pass empty subject");

	global $phc;
	global $opt_long;

	if ($opt_long)
		$max_exe = 12;
	else
		$max_exe = 5;

	$dir_path = dirname($subject);
	return "$phc -d include_path=./:test/subjects/:$dir_path -d max_execution_time=$max_exe $subject ";
}



// Get a subject in the forms "test/subjects/codegen/000.php.out", and return "test_subjects_codegen_000.php.out"
function wd_name ($subject)
{
	global $working_directory;
	return "$working_directory/". preg_replace ("/\//", "_", $subject);
}


$strict = false;
function phc_error_handler ($errno, $errstr, $errfile, $errline, $errcontext)
{
	global $strict;
	if (error_reporting() == 0) return; // means the @ suppression was used
	if ($errno === E_STRICT && $strict == false) return;

	$red = red_string ();
	$reset = reset_string();
	$blue = blue_string();

	print "\n$red-----Error----------$reset\n";

	print "\n$red----Context---------$reset\n";
	$long_dump = "";
	foreach ($errcontext as $name => $context)
	{
		$long_dump .= sprintf("$blue$%-12s$reset => ", $name);
		$dump = preg_replace ("/\n/", " ", print_r($context, true));
		$long_dump .= "$dump; $reset\n";
	}
	if (strlen ($long_dump) > 640)
	{
		print "{$red}Context skipped, too long (" .strlen($long_dump).")";
	}
	else
	{
		print $long_dump;
	}
	print "\n";

	print "\n$red----Backtrace-------$reset\n";
	$backtrace = debug_backtrace ();
	$prev_frame = $backtrace[0];

	$j = 0; // use j for printing
	/* skip listing this function */
	for ($i = 1; $i < count($backtrace); $i++)
	{
		$frame = $backtrace[$i];
		$function = $frame{"function"};
		$object = $frame{"object"};
		$class = get_class($frame{"object"});
		$type = $frame{"type"};

		/* skip trigger_error and multiple asserts */
		if (($function == "trigger_error")
				or ($function == "phc_assert" 
					and
					$backtrace[$i+1]{"function"} == "phc_unreachable"))
		{
			$prev_frame = $frame;
			continue;
		}


		/* make the arg string readable */
		$args = $frame{"args"};
		if ($args == NULL) $args = array (); // sometimes args is NULL
		foreach ($args as &$arg) // cant convert objects to strings
		{
			if (is_object ($arg))
				$arg = "OBJECT";
		}
		$args_string = join(", ", $args);
		$args_string = preg_replace ("/\n/", "", $args_string);
		$args_string = substr ($args_string, 0, 30);
		if (strlen ($args_string) == 30) 
			$args_string .= "...";

		/* print the frame info */
		if ($prev_frame === NULL)
		{
			print "#$j $class$type$function ($args_string)\n";
		}
		else
		{
			$file = $prev_frame{"file"};
			$file = preg_replace ("!^.*/framework/(.*$)!", "$1", $file);
			$line = $prev_frame{"line"};
			print "#$j $class$type$function ($args_string) at $file:$line\n";
		}
		$prev_frame = $frame;
		$j++;
	}

	/* print the final frame */
	$file = $prev_frame{"file"};
	$file = preg_replace ("!^.*/framework/(.*$)!", "$1", $file);
	$line = $prev_frame{"line"};
	print "#$j called from $file:$line\n";


	print "\n$red----Message---------$reset\n";
	die(sprintf ("Error ($errno): '$errstr' at $errfile:$errline\n"));
}
set_error_handler ("phc_error_handler");

function open_status_files ()
{
	global $status_files;
	global $log_directory;
	foreach (array ("failure", "skipped", "success", "timeout", "results", "pure", "impure") as $status)
	{
		$status_files[$status] = fopen ("$log_directory/$status", "w") or die ("Cannot open $status file\n");
	}
}

function log_status ($status, $test_name, $subject, $reason)
{
	$status_name = ucfirst ($status);
	if ($reason != "") $reason = " - $reason";
	write_status ($status, "$test_name: $status_name $subject$reason\n");
}

function write_status ($status, $string)
{
	global $status_files;
	$file = $status_files[$status];

	fwrite ($file, $string);

	// we frequently stop the test midway, but we want up to the minute results
	fflush ($file);
}

function close_status_files ()
{
	global $status_files;
	foreach ($status_files as $file)
		fclose ($file);
}

function phc_assert ($boolean, $message)
{
	if (!$boolean)
	{
		trigger_error ($message);
	}
}

function phc_unreachable ($message = "This point should be unreachable")
{
	phc_assert (false, $message);
}

function reset_string()
{
	return sprintf("%c[0m", 27);
}

// blue foreground color 
function blue_string()
{
	return sprintf("%c[1;34m", 27);
}

// green foreground color 
function green_string()
{
	return sprintf("%c[1;32m", 27);
}

// red foreground color 
function red_string()
{
	return sprintf("%c[1;31m", 27);
}

function strip_colour	($string)
{
	$codes = array (red_string (), green_string (), blue_string (), reset_string ());
	return str_replace ($codes, "", $string);
}


// returns true if the machine is 32 bit (based on integer arithmetic)
function is_32_bit()
{
	# 2147483648 == 2^31
	if (is_integer(2147483648  + 10)) # this should convert to a real on a 32 bit machine
	{
		return false;
	}
	return true;
}

/* Prints diffs if the xdiff extension is available, and simple outputs both
 * strings otherwise. 
 * To install xdiff:
 *   install libxdiff from http://www.xmailserver.org/xdiff-lib.html 
 *   install xdiff from pecl with "pecl install xdiff". 
 *   There is no need to load xdiff.so in your php.ini file. */
function diff ($string1, $string2)
{
	if (!extension_loaded ("xdiff"))
	{
		$result = @dl ("xdiff.so"); // avoid the E_WARNING
		if (!$result)
		{
			return "Note: xdiff not available for diffing. Outputting both strings:\nString1:\n$string1\nString2:\n$string2";
		}
	}
	return xdiff_string_diff ("$string1\n", "$string2\n");
}

function log_failure ($test_name, $subject, $commands, $outs, $errs, $exits, $missing_dependency, $reason)
{
	$red = red_string();
	$reset = reset_string();

	// we have 1 or more outputs, only 1 error, and 1 or more commands, with the same number of error codes as commands
	$err_string = "";
	if (is_array ($commands))
	{
		if ($exits == "Not relevent")
		{
			$command_string = "";
			foreach ($commands as $command)
			{
				$command_string .= "{$red}Command:$reset $command\n";
			}
		}
		else
		{
			phc_assert (is_array ($exits), "Expected array of return values");
			phc_assert (count ($exits) == count ($errs), 
					"Expected same number of exits as exit codes");

			$command_string = "";
			$err_string = "";
			foreach ($exits as $i => $exit)
			{
				$command = $commands[$i];
				$err = $errs [$i];
				$command_string .= "{$red}Command $i$reset ($exit): $command\n";
				if ($err)
					$err_string .= "{$red}Error $i$reset: $err\n";
			}
		}

	}
	else
	{
		$command_string = "{$red}Command$reset ($exits): $commands\n";
		if ($errs)
			$err_string = "{$red}Error$reset: $errs\n";
	}

	$reason_string = "Reason: $reason\n";

	$dependency_string = "";
	if ($missing_dependency)
	{
		$dependency_string = "NOTE: dependency $missing_dependency is missing. This may be the cause of this failure\n";
	}

	$header = "$reason_string$dependency_string$command_string$err_string";

	// ready the output information
	global $log_directory;
	$script_name = adjusted_name ($subject);
	$filename = "$log_directory/$test_name/$script_name.log";
	$dirname = dirname($filename);
	if (!is_dir ($dirname))
	{
		@mkdir($dirname, 0755, true);
		phc_assert (is_dir($dirname), "directory not created");
	}


	if (!is_array ($outs))
		$outs = array ($outs);

	$out_string = "";
	// create the stdout logs - but only if there is more than 1 log
	if (count ($outs) > 1 && count (array_filter ($outs, "strlen")))
	{
		foreach ($outs as $i => $out)
		{
			$output_contents = "Command: ${commands[$i]}\n" . $out;
			file_put_contents ("$filename.out.$i", $output_contents);
		}
	}

	// write to the main log file
	foreach ($outs as $i => $out)
	{
		if (strlen ($out) > 1000)
			$out = substr ($out, 0, 1000) . "... [truncated]\n";

		$out_string .= "{$red}Output $i$reset:\n$out\n";
	}

	// print the output
	file_put_contents($filename, $header);
	file_put_contents($filename, rtrim($out_string), FILE_APPEND);
	file_put_contents($filename, "\n", FILE_APPEND);
}

function adjusted_name ($script_name, $adjust_for_regression = 0)
{
	global $subject_dir;
	phc_assert($subject_dir !== "", "subject_dir must be defined before this function is called");

	// we need the prefix for the check
	if($adjust_for_regression)
	{
		// add the size-dependent suffix
		if (is_labelled($script_name, "size-dependent"))
		{
			if(is_32_bit())
			{
				$script_name .= ".32bit";
			}
			else
			{
				$script_name .= ".64bit";
			}
		}
	}

	// remove the prefix
	$prefix = $subject_dir;
	$prefix = preg_replace("/\//", "\\/", $prefix);
	$script_name = preg_replace("/$prefix/", "", $script_name);

	return $script_name;
}

function complete_exec($command, $stdin = NULL, $timeout = 20, $pass_through = false)
{
	global $opt_verbose;
	if ($opt_verbose)
		print "Running command: $command\n";

	$descriptorspec = array(0 => array("pipe", "r"),
									1 => array("pipe", "w"),
									2 => array("pipe", "w"));
	$pipes = array();
	$handle = proc_open($command, $descriptorspec, &$pipes, getcwd());
	
	# read stdin into the process
	if ($stdin !== NULL)
		fwrite ($pipes[0], $stdin);

	fclose ($pipes[0]);
	unset ($pipes[0]);

	// set non blocking to avoid infinite loops on stuck programs
	stream_set_blocking ($pipes[1], 0);
	stream_set_blocking ($pipes[2], 0);

	$out = "";
	$err = "";

	$start_time = time ();
	do
	{
		$status = proc_get_status ($handle);

		// It seems that with a large amount fo output, the process
		// won't finish unless the buffers are periodically cleared.
		// (This doesn't seem to be the case is async_test. I don't
		// know why).
		$new_out = stream_get_contents ($pipes[1]);
		$new_err = stream_get_contents ($pipes[2]);
		$out .= $new_out;
		$err .= $new_err;

		if ($pass_through)
		{
			print $new_out;
			file_put_contents ("php://stderr", $new_err);
		}

		if ($timeout != 0
			&& time () > $start_time + $timeout)
		{
			$out = stream_get_contents ($pipes[1]);
			$err = stream_get_contents ($pipes[2]);

			kill_properly ($handle, $pipes);

			return array ("Timeout", $out, $err);
		}

		// Since we use non-blocking, the for loop could well take 100%
		// CPU. time of 1000 - 10000 seems OK. 100000 slows down the
		// program by 50%.
		usleep (10000);
	}
	while ($status["running"]);
	stream_set_blocking ($pipes[1], 1);
	stream_set_blocking ($pipes[2], 1);
	$out .= stream_get_contents ($pipes[1]);
	$err .= stream_get_contents ($pipes[2]);

	$exit_code = $status["exitcode"];
	
	kill_properly ($handle, $pipes);

	return array ($out, $err, $exit_code);
}

# Kill the process, and close the pipes.
function kill_properly (&$handle, &$pipes)
{
	$status = proc_get_status ($handle);

	# proc_terminate kills the shell process, but won't kill a runaway infinite
	# loop. Get the child processes using ps, before killing the parent.
	$ppid = $status["pid"];
	$pids = split ("/\s+/", trim (`ps -o pid --no-heading --ppid $ppid`));

	# if we dont close pipes, we can create deadlock, leaving zombie processes.
	foreach ($pipes as &$pipe) fclose ($pipe);
	proc_terminate ($handle);
	proc_close ($handle);

	// Not necessarily available.
	if (function_exists ("posix_kill"))
	{
		foreach ($pids as $pid)
		{
			if (is_numeric ($pid))
				posix_kill ($pid, 9);
		}
	}
}

function check_for_plugin ($plugin_name)
{
	global $plugin_dir;
	return file_exists ("$plugin_dir/$plugin_name.la");
}

function check_for_program ($program_name)
{
	// only use the first word
	$program_names = split (" ", $program_name);
	$program_name = $program_names[0];

	return ($program_name !== "" 
			&& (file_exists ($program_name) or $program_name == "gcc"));
}

function date_string ()
{
	# only generate once
	# Day_date_month_hour_min_sec
	global $date_string;
	if ($date_string == NULL)
	{
		$date_string = date ("D_d_M_H_i_s");
	}

	return $date_string;

}

/* These are used to store dependencies. When a test is run, we dont want to
 * display errors for tests which we know will fail, since they failed a
 * previous test, and are guaranteed to fail this one. I tried a number of
 * other approaches to this, including keeping a single set of results in a
 * file, or a file per test run, or a file per test, and they all had flaws.
 * 
 * There are a number of odd problems. First, any number of tests can be run,
 * and you can run a test without running its dependencies, and it is desirable
 * to do so. Secondly, a test run can stop at any point, due to an exit or a
 * CTRL-C. Thirdly, multiple test runs can occur simulateously, and shouldnt
 * interfere with each other. In all these cases, incorrect behaviour can lead
 * to dependencies being incorrectly guessed, or overwritten with incorrect
 * information, or not available at all. This will result in people searching
 * for bugs in the wrong places, or not being able to identify actual bugs.
 *
 * The final chosen solution is to have a single directory (test/dependecnies/),
 * containing one directory for each test, and each of these containing 1 file
 * per test subject. The contents of this file is "Pass" or "Fail". The file is
 * read each time we want to resolve a dependency, and never cached. 
 *
 * If the file doesnt exist, then we cant resolve the dependency, which works
 * out the same as a "Pass", in that dependent tests will run. This is the only
 * time we can get incorrect information. In this case, we mark the failure
 * log, so that we dont go on wild goose chases without being told.
 */

function get_dependent_filename ($test_name, $subject)
{
	$result = "test/dependencies/$test_name/$subject";

	// make sure the directory exists
	if (!file_exists (dirname ($result)))
	{
		$is_made = mkdir (dirname ($result), 0700, true);
		assert ($is_made);
	}

	return $result;
}
function write_dependencies ($test_name, $subject, $value)
{
	$filename = get_dependent_filename ($test_name, $subject);
	if ($value == true)
	{
		file_put_contents ($filename, "Pass");
	}
	else
	{
		file_put_contents ($filename, "Fail");
	}
}

function read_dependency ($test_name, $subject)
{
	$filename = get_dependent_filename ($test_name, $subject);
	if (!file_exists ($filename))
		return "missing";
	else
		return file_get_contents ($filename);
}



function homogenize_xml ($string)
{
	$string = preg_replace("/(<attr key=\"phc.line_number\">)\d+(<\/attr>)/", "$1$2", $string);
	$string = preg_replace("/(<attr key=\"phc.filename\">).*?(<\/attr>)/", "$1$2", $string);

	// fresh doesnt return the same numbers every time.
	$string = preg_replace("/(<value>\D+)\d+(<\/value>)/", "$1xx$2", $string);
	$string = preg_replace("/(<string>\D+)\d+(<\/string>)/", "$1xx$2", $string);
	return $string;
}

function homogenize_filenames_and_line_numbers ($string, $filename)
{
	global $base_dir;

	// This doesnt work for install tests.
	$stdin_filename = "$base_dir/-";
	$full_filename = "$base_dir/$filename";


	// Remove 'Unknown:'
	$string = preg_replace( "/Warning: Unknown:/", "Warning:", $string);
	$string = preg_replace( "/fatal error: Unknown:/", "Fatal error:", $string);
	$string = preg_replace( "/Catchable fatal error: Unknown:/", "Catchable fatal error:", $string);


	// Sometimes there is a filename, sometimes not. Easiest to leave as is,
	// rather than trying to coerce __FILENAME__ into it.
	$string = preg_replace( "/(Warning: )(\S*: )?(.+? in )\S+ on line \d+/", "$1$3", $string);
	$string = preg_replace( "/(Fatal error: )(\S*: )?(.+? in )\S+ on line \d+/", "$1$3", $string);
	$string = preg_replace( "/(Catchable fatal error: .+? in )\S+ on line \d+/", "$1", $string);

	$string = preg_replace( "/on line \d+/", "on line __LINE__", $string);
	$string = preg_replace( "!$full_filename(:\d+)?!", "__FILENAME__", $string);
	$string = preg_replace( "!\[no active file\](:\d+)?!", "__FILENAME__", $string);
	$string = preg_replace( "!$filename(:\d+)?!", "__FILENAME__", $string);
	$string = preg_replace( "!$stdin_filename(:\d+)?!", "__FILENAME__", $string);

	$string = preg_replace( "!__FILENAME__ on line __LINE__!", "", $string);

//	$string = preg_replace( "/(Fatal error: Allowed memory size of )\d+( bytes exhausted at )\S*( \(tried to allocate )\d+( bytes\) in )\S*( on line )\d+/", "$1$2$3$4", $string);

	return $string;
}

function homogenize_break_levels ($string)
{
	// this is a bug we want to ignore
	$string = preg_replace( "/(Fatal error: Cannot break\/continue 1 level)s/", "$1", $string);
	return $string;
}

// This strips off the & from a var_dump. Changing whether something
// is a reference or not isnt correct, but changing the refcount is
// ok. If a var only has a reference count of 1, then is_ref wont be
// set, so the & wont be present.
function homogenize_reference_count ($string)
{
	// A var dump for an array looks like this:
	//		array(2) {
	//			["a"]=>
	//			&string(1) "a"
	//			["b"]=>
	//			string(1) "b"
	//		}
	$string = preg_replace(
		"/
			(\s+\[.*?\]=>\s+)		# key and newline
			&							# we want to delete this
			(.*?)						# dont go too far
		/Ssmx", "$1$2", $string); // s=DOTALL,m=MULTILINE,x=EXTENDED,S=STUDY(?)
	return $string;
}

/* We do not care if there is one object, or two objects alive at any point.
 * However, var_dump will give different results depending on the number of
 * live objects of a particular type. Since we shred, we make the extra objects
 * stay alive longer. So mask it from testing. */
function homogenize_object_count ($string)
{
	// A var dump for an object looks like this:
	# object(Foo)#1 (2) {
	#   ["x1"]=>
	#   string(8) "Foo::Bar"
	#   ["x2"]=>
	#   array(1) {
	#     [0]=>
	#     string(8) "Foo::Bar"
	#   }
	# }

	$string = preg_replace("/^(object\(\w+\)#)\d+/m", "$1x", $string);
	return $string;
}

function homogenize_all ($string, $filename)
{
	$string = homogenize_reference_count ($string);
	$string = homogenize_object_count ($string);
	$string = homogenize_filenames_and_line_numbers ($string, $filename);
	$string = homogenize_break_levels ($string);
	return $string;
}

function copy_to_working_dir ($file)
{
	global $working_directory;
	$filename = basename ($file);
	$new_file = "$working_directory/$filename";
	copy ($file, $new_file);
	chmod ($new_file, fileperms ($file));
	return $new_file;
}

?>
Return current item: phc