snapshot_directory
in '
+ . 'app/config/bootstrap.php
exists and '
+ . 'has the proper permissions.'
+ );
+ }
+
+ fwrite($handle, $contents);
+ fclose($handle);
+ return array(
+ 'type' => 'succeeded',
+ 'title' => 'Snapshot Created',
+ 'message' => "Snapshot can be found at {$filename}
."
+ );
+ }
+
+ // GET
+ public function help($request) {
+ return array();
+ }
+
+ // GET/POST
+ public function index($request) {
+ if ( $request->is('get') ) {
+ $normalize_path = function($path) {
+ return str_replace('\\', '/', realpath($path));
+ };
+ $test_directories = json_encode(array_map(
+ $normalize_path, \app\lib\Library::retrieve('test_directories')
+ ));
+
+ $suites = array();
+ $stats = array();
+ $store_statistics = \app\lib\Library::retrieve('store_statistics');
+ $create_snapshots = \app\lib\Library::retrieve('create_snapshots');
+ $sandbox_errors = \app\lib\Library::retrieve('sandbox_errors');
+ $xml_configuration_files = \app\lib\Library::retrieve(
+ 'xml_configuration_files'
+ );
+ return compact(
+ 'create_snapshots',
+ 'sandbox_errors',
+ 'stats',
+ 'store_statistics',
+ 'suites',
+ 'test_directories',
+ 'xml_configuration_files'
+ );
+ }
+
+ $tests = explode('|', $request->data['test_files']);
+ $vpu = new \app\lib\VPU();
+
+ if ( $request->data['sandbox_errors'] ) {
+ error_reporting(\app\lib\Library::retrieve('error_reporting'));
+ set_error_handler(array($vpu, 'handle_errors'));
+ }
+
+ $xml_config = false;
+
+ $notifications = array();
+ if ( $xml_file_index = $request->data['xml_configuration_file'] ) {
+ $files = \app\lib\Library::retrieve('xml_configuration_files');
+ $xml_config = $files[$xml_file_index - 1];
+ if ( !$xml_config || !$xml_config = realpath($xml_config) ) {
+ $notifications[] = array(
+ 'type' => 'failed',
+ 'title' => 'No Valid XML Configuration File Found',
+ 'message' => 'Please ensure that the '
+ . 'xml_configuration_file
in '
+ . 'app/config/bootstrap.php
exists and '
+ . 'has the proper permissions.'
+ );
+ }
+ }
+
+ $results = ( $xml_config )
+ ? $vpu->run_with_xml($xml_config)
+ : $vpu->run_tests($tests);
+ $results = $vpu->compile_suites($results, 'web');
+
+ if ( $request->data['sandbox_errors'] ) {
+ restore_error_handler();
+ }
+
+ $suites = $results['suites'];
+ $stats = $results['stats'];
+ $errors = $vpu->get_errors();
+ $to_view = compact('suites', 'stats', 'errors');
+
+ if ( $request->data['create_snapshots'] ) {
+ $notifications[] = $this->_create_snapshot($to_view);
+ }
+ if ( $request->data['store_statistics'] ) {
+ $notifications[] = $this->_store_statistics($stats);
+ }
+
+ return $to_view + compact('notifications');
+ }
+
+ protected function _store_statistics($stats) {
+ $db_options = \app\lib\Library::retrieve('db');
+ $db = new $db_options['plugin']();
+ if ( !$db->connect($db_options) ) {
+ return array(
+ 'type' => 'failed',
+ 'title' => 'Error Connecting to Database',
+ 'message' => implode(' ', $db->get_errors())
+ );
+ }
+
+ $now = date('Y-m-d H:i:s');
+ foreach ( $stats as $key => $stat ) {
+ $data = array(
+ 'run_date' => $now,
+ 'failed' => $stat['failed'],
+ 'incomplete' => $stat['incomplete'],
+ 'skipped' => $stat['skipped'],
+ 'succeeded' => $stat['succeeded']
+ );
+ $table = ucfirst(rtrim($key, 's')) . 'Result';
+ if ( !$db->insert($table, $data) ) {
+ return array(
+ 'type' => 'failed',
+ 'title' => 'Error Inserting Record',
+ 'message' => implode(' ', $db->get_errors())
+ );
+ }
+ }
+
+ return array(
+ 'type' => 'succeeded',
+ 'title' => 'Statistics Stored',
+ 'message' => 'The statistics generated during this test run were '
+ . 'successfully stored.'
+ );
+
+ }
+
+}
+
+?>
diff --git a/vpu/app/core/Controller.php b/vpu/app/core/Controller.php
new file mode 100644
index 0000000..6701284
--- /dev/null
+++ b/vpu/app/core/Controller.php
@@ -0,0 +1,140 @@
+ array(
+ 'view' => 'app\core\View'
+ )
+ );
+ $this->_config = $config + $defaults;
+ }
+
+ /**
+ * Primary entry point for all controller actions. The supplied action is
+ * called, returning a response which is then filtered based on the request
+ * source (e.g., xhr, web).
+ *
+ * @param string $action The method to be called.
+ * @param obj $request The request object.
+ * @access public
+ * @return array
+ */
+ public function call($action, $request) {
+ $results = $this->$action($request);
+
+ if ( is_null($results) || $results === false ) {
+ return false;
+ }
+
+ if ( !is_array($results) ) {
+ $this->_response['body'] = $results;
+ return $this->_response;
+ }
+
+ if ( $request->is('ajax') ) {
+ $this->_response['body'] = $this->render_json($results);
+ } else {
+ $class = explode('\\', get_called_class());
+ $classname = end($class);
+ $file = lcfirst($classname) . "/{$action}";
+ $this->_response['body'] = $this->render_html($file, $results);
+ }
+
+ return $this->_response;
+ }
+
+ /**
+ * Redirects the page.
+ *
+ * @param string $page The page to be redirected to.
+ * @access public
+ * @return bool
+ */
+ public function redirect($page) {
+ $this->set_response_status(303);
+ $this->set_response_headers(array('Location: ' . $page));
+ return '';
+ }
+
+ /**
+ * Converts the supplied value to JSON.
+ *
+ * @param mixed $value The value to encode.
+ * @access public
+ * @return string
+ */
+ public function render_json($value) {
+ $options = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP;
+ return json_encode($value, $options);
+ }
+
+ /**
+ * Renders a view.
+ *
+ * @param string $action The file to be rendered.
+ * @param array $vars The variables to be substituted in the view.
+ * @access public
+ * @return string
+ */
+ public function render_html($file, $vars = array()) {
+ $view = $this->_config['dependencies']['view'];
+ $view = new $view();
+ return $view->render($file, $vars);
+ }
+
+ /**
+ * Sets the response headers. Note that the supplied headers must be
+ * well-formed HTTP headers. Example:
+ *
+ * $headers = array('Content-Type: text/html; charset=utf-8');
+ *
+ * @param array $headers The response headers.
+ * @access public
+ * @return void
+ */
+ public function set_response_headers($headers) {
+ $this->_response['headers'] = $headers;
+ }
+
+ /**
+ * Sets the response status. Note that the supplied status must be the
+ * integer associated with the HTTP status code (e.g., 404 for Not Found).
+ *
+ * @param int $status The response status.
+ * @access public
+ * @return void
+ */
+ public function set_response_status($status) {
+ $this->_response['status'] = $status;
+ }
+
+}
+
+?>
diff --git a/vpu/app/core/View.php b/vpu/app/core/View.php
new file mode 100644
index 0000000..6790ae0
--- /dev/null
+++ b/vpu/app/core/View.php
@@ -0,0 +1,70 @@
+ array(
+ 'compiler' => 'app\lib\Compiler',
+ )
+ );
+ $this->_config = $config + $defaults;
+ }
+
+ /**
+ * Escapes a value for output in an HTML context.
+ *
+ * @param mixed $value
+ * @access public
+ * @return mixed
+ */
+ public function escape($value) {
+ return nl2br(htmlspecialchars($value, ENT_QUOTES, 'UTF-8'));
+ }
+
+ /**
+ * Renders a given file with the supplied variables.
+ *
+ * @param string $file The file to be rendered.
+ * @param mixed $vars The variables to be substituted in the view.
+ * @access public
+ * @return string
+ */
+ public function render($file, $vars = null) {
+ $path = dirname(__DIR__) . '/resource/cache/';
+ $file = dirname(__DIR__) . "/view/{$file}.html";
+
+ $compiler = $this->_config['dependencies']['compiler'];
+ $options = compact('path');
+ $__template__ = $compiler::compile($file, $options);
+
+ if ( !$__template__ ) {
+ throw new \RuntimeException(
+ 'Could not write cache file. Please ensure that the '
+ . "permissions of {$path} are correct."
+ );
+ }
+
+ if ( is_array($vars) ) {
+ extract($vars);
+ }
+
+ ob_start();
+ require $__template__;
+ return ob_get_clean();
+ }
+
+}
+
+?>
diff --git a/vpu/app/lib/Compiler.php b/vpu/app/lib/Compiler.php
new file mode 100644
index 0000000..0dcda25
--- /dev/null
+++ b/vpu/app/lib/Compiler.php
@@ -0,0 +1,82 @@
+ 'compiled/'
+ );
+
+ $stats = stat($file);
+ $dir = dirname($file);
+ $location = basename(dirname($dir)) . '_' . basename($dir)
+ . '_' . basename($file, '.html');
+ $template = 'template_' . $location . '_' . $stats['mtime']
+ . '_' . $stats['ino'] . '_' . $stats['size'] . '.html';
+ $template = $options['path'] . $template;
+
+ if ( file_exists($template) ) {
+ return $template;
+ }
+
+ $compiled = self::_replace(file_get_contents($file));
+ $template_dir = dirname($template);
+ if ( !is_dir($template_dir) && !mkdir($template_dir, 0755, true) ) {
+ return false;
+ }
+
+ if (
+ !is_writable($template_dir)
+ || file_put_contents($template, $compiled) === false
+ ) {
+ return false;
+ }
+
+ $pattern = $template_dir . '/template_' . $location . '_*.html';
+ foreach ( glob($pattern) as $old ) {
+ if ( $old !== $template ) {
+ unlink($old);
+ }
+ }
+ return $template;
+ }
+
+ /**
+ * Replaces a template with custom syntax.
+ *
+ * @param string $template The template.
+ * @access public
+ * @return string
+ */
+ protected static function _replace($template) {
+ $replace = array(
+ '/\<\?=\s*\$this->(.+?)\s*;?\s*\?>/msx' =>
+ '$1; ?>',
+
+ '/\$e\((.+?)\)\s*;/msx' =>
+ 'echo $this->escape($1);',
+
+ '/\<\?=\s*(.+?)\s*;?\s*\?>/msx' =>
+ 'escape($1); ?>'
+ );
+
+ return preg_replace(
+ array_keys($replace), array_values($replace), $template
+ );
+ }
+
+}
diff --git a/vpu/app/lib/Library.php b/vpu/app/lib/Library.php
new file mode 100644
index 0000000..5b7b4fa
--- /dev/null
+++ b/vpu/app/lib/Library.php
@@ -0,0 +1,44 @@
+
diff --git a/vpu/app/lib/PDO_MySQL.php b/vpu/app/lib/PDO_MySQL.php
new file mode 100644
index 0000000..692d807
--- /dev/null
+++ b/vpu/app/lib/PDO_MySQL.php
@@ -0,0 +1,319 @@
+_affected_rows;
+ }
+
+ /**
+ * Closes the connection.
+ *
+ * @access public
+ * @return bool
+ */
+ public function close() {
+ $this->_dbh = null;
+ return true;
+ }
+
+ /**
+ * Connects and selects database.
+ *
+ * @param array $options Contains the connection information. Takes the
+ * following options:
+ * 'database' - The name of the database.
+ * 'host' - The database host.
+ * 'port' - The database port.
+ * 'username' - The database username.
+ * 'password' - The database password.
+ * @access public
+ * @return bool
+ */
+ public function connect($options = array()) {
+ $dsn = "mysql:host={$options['host']};port={$options['port']}"
+ . ";dbname={$options['database']}";
+ try {
+ $this->_dbh = new PDO($dsn, $options['username'], $options['password']);
+ $this->_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ return true;
+ } catch ( PDOException $e ) {
+ $this->_errors[] = $e->getMessage();
+ return false;
+ }
+ }
+
+ /**
+ * Fetches the next row from the result set in memory (i.e., the one
+ * that was created after running query()).
+ *
+ * @param string $fetch_style Controls how the rows will be returned.
+ * @param obj $obj The object to be fetched into if
+ * $fetch_style is set to 'into'.
+ * @access public
+ * @return mixed
+ */
+ public function fetch($fetch_style = null, $obj = null) {
+ $this->_set_fetch_mode($fetch_style, $obj);
+ $row = $this->_statement->fetch();
+ $this->_statement->closeCursor();
+ return $row;
+ }
+
+ /**
+ * Returns an array containing all of the result set rows.
+ *
+ * @param string $fetch_style Controls how the rows will be returned.
+ * @access public
+ * @return mixed
+ */
+ public function fetch_all($fetch_style = null) {
+ $this->_set_fetch_mode($fetch_style);
+ $rows = $this->_statement->fetchAll();
+ $this->_statement->closeCursor();
+ return $rows;
+ }
+
+ /**
+ * Returns a single column from the next row of a result set or false
+ * if there are no more rows.
+ *
+ * @param int $column_number Zero-index number of the column to
+ * retrieve from the row.
+ * @access public
+ * @return mixed
+ */
+ public function fetch_column($column_number = 0) {
+ $column = $this->_statement->fetchColumn($column_number);
+ $this->_statement->closeCursor();
+ return $column;
+ }
+
+ /**
+ * Returns the errors generated by PDOExceptions.
+ *
+ * @access public
+ * @return array
+ */
+ public function get_errors() {
+ return $this->_errors;
+ }
+
+ /**
+ * Inserts a record into the database.
+ *
+ * @param string $table The table containing the record to be inserted.
+ * @param array $data An array containing the data to be inserted.
+ * Format should be as follows:
+ * array('column_name' => 'column_value');
+ * @access public
+ * @return bool
+ */
+ public function insert($table, $data) {
+ $sql = "INSERT INTO {$table} ";
+
+ $key_names = array_keys($data);
+ $fields = implode(', ', $key_names);
+ $values = ':' . implode(', :', $key_names);
+
+ $sql .= "({$fields}) VALUES ({$values})";
+
+ $statement = $this->_dbh->prepare($sql);
+
+ try {
+ $statement->execute($data);
+ } catch ( PDOException $e ) {
+ $this->_errors[] = $e->getMessage();
+ return false;
+ }
+
+ $this->_affected_rows = $statement->rowCount();
+ return true;
+ }
+
+ /**
+ * Returns the ID of the last inserted row or sequence value.
+ *
+ * @access public
+ * @return int
+ */
+ public function insert_id() {
+ return $this->_dbh->lastInsertId();
+ }
+
+ /**
+ * Executes SQL query.
+ *
+ * @param string $sql The SQL query to be executed.
+ * @param array $parameters An array containing the parameters to be
+ * bound.
+ * @access public
+ * @return bool
+ */
+ public function query($sql, $parameters = array()) {
+ $statement = $this->_dbh->prepare($sql);
+
+ foreach ( $parameters as $index => $parameter ) {
+ $statement->bindValue($index + 1, $parameter);
+ }
+
+ try {
+ $statement->execute();
+ } catch ( PDOException $e ) {
+ $this->_errors[] = $e->getMessage();
+ return false;
+ }
+
+ $this->_affected_rows = $statement->rowCount();
+ $this->_statement = $statement;
+ return true;
+ }
+
+ /**
+ * Sets the fetch mode.
+ *
+ * @param string $fetch_style Controls how the rows will be returned.
+ * @param obj $obj The object to be fetched into for use with
+ * FETCH_INTO.
+ * @access protected
+ * @return int
+ */
+ protected function _set_fetch_mode($fetch_style, $obj = null) {
+ switch ( $fetch_style ) {
+ case 'assoc':
+ $this->_statement->setFetchMode(PDO::FETCH_ASSOC);
+ break;
+ case 'into':
+ $this->_statement->setFetchMode(PDO::FETCH_INTO, $obj);
+ break;
+ default:
+ $this->_statement->setFetchMode(PDO::FETCH_ASSOC);
+ break;
+ }
+ }
+
+ /**
+ * Updates a record in the database.
+ *
+ * @param string $table The table containing the record to be inserted.
+ * @param array $data An array containing the data to be inserted.
+ * Format should be as follows:
+ * array('column_name' => 'column_value');
+ * @param array $where The WHERE clause of the SQL query.
+ * @access public
+ * @return bool
+ */
+ public function update($table, $data, $where = null) {
+ $sql = "UPDATE {$table} SET ";
+
+ $key_names = array_keys($data);
+ foreach ( $key_names as $name ) {
+ $sql .= "{$name}=:{$name}, ";
+ }
+
+ $sql = rtrim($sql, ', ');
+
+ if ( !is_null($where) ) {
+ $sql .= ' WHERE ';
+ foreach ( $where as $name => $val ) {
+ $sql .= "{$name}=:{$name}_where, ";
+ $data["{$name}_where"] = $val;
+ }
+ }
+ $statement = $this->_dbh->prepare($sql);
+
+ try {
+ $statement->execute($data);
+ } catch ( PDOException $e ) {
+ $this->_errors[] = $e->getMessage();
+ return false;
+ }
+
+ $this->_affected_rows = $statement->rowCount();
+ return true;
+ }
+
+ /**
+ * Inserts or updates (if exists) a record in the database.
+ *
+ * @param string $table The table containing the record to be inserted.
+ * @param array $data An array containing the data to be inserted.
+ * Format should be as follows:
+ * array('column_name' => 'column_value');
+ * @access public
+ * @return bool
+ */
+ public function upsert($table, $data) {
+ $sql = "INSERT INTO {$table}";
+
+ $key_names = array_keys($data);
+ $fields = implode(', ', $key_names);
+ $values = ':' . implode(', :', $key_names);
+
+
+ $sql .= "({$fields}) VALUES ({$values}) ON DUPLICATE KEY UPDATE ";
+
+ foreach ( $key_names as $name ) {
+ $sql .= "{$name}=:{$name}, ";
+ }
+
+ $sql = rtrim($sql, ', ');
+ $statement = $this->_dbh->prepare($sql);
+
+ try {
+ $statement->execute($data);
+ } catch ( PDOException $e ) {
+ $this->_errors[] = $e->getMessage();
+ return false;
+ }
+
+ $this->_affected_rows = $statement->rowCount();
+ return true;
+ }
+}
+
+?>
diff --git a/vpu/app/lib/VPU.php b/vpu/app/lib/VPU.php
new file mode 100644
index 0000000..8bddb19
--- /dev/null
+++ b/vpu/app/lib/VPU.php
@@ -0,0 +1,507 @@
+ $stats ) {
+ $results[$name] = $stats;
+ foreach ( $stats as $key => $value ) {
+ if ( $key == 'total' ) {
+ continue;
+ }
+ // Avoid divide by zero error
+ if ( $stats['total'] ) {
+ $results[$name]['percent' . ucfirst($key)] =
+ round($stats[$key] / $stats['total'] * 100, 1);
+ } else {
+ $results[$name]['percent' . ucfirst($key)] = 0;
+ }
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Returns the class name without the namespace.
+ *
+ * @param string $class The class name.
+ * @access protected
+ * @return string
+ */
+ protected function _classname_only($class) {
+ $name = explode('\\', $class);
+ return end($name);
+ }
+
+ /**
+ * Organizes the output from PHPUnit into a more manageable array
+ * of suites and statistics.
+ *
+ * @param string $pu_output The JSON output from PHPUnit.
+ * @param string $source The executing source (web or cli).
+ * @access public
+ * @return array
+ */
+ public function compile_suites($pu_output, $source) {
+ $results = $this->_parse_output($pu_output);
+
+ $collection = array();
+ $statistics = array(
+ 'suites' => array(
+ 'succeeded' => 0,
+ 'skipped' => 0,
+ 'incomplete' => 0,
+ 'failed' => 0,
+ 'total' => 0
+ )
+ );
+ $statistics['tests'] = $statistics['suites'];
+ foreach ( $results as $result ) {
+ if ( !isset($result['event']) || $result['event'] != 'test' ) {
+ continue;
+ }
+
+ $suite_name = $this->_classname_only($result['suite']);
+
+ if ( !isset($collection[$suite_name]) ) {
+ $collection[$suite_name] = array(
+ 'tests' => array(),
+ 'name' => $suite_name,
+ 'status' => 'succeeded',
+ 'time' => 0
+ );
+ }
+ $result = $this->_format_test_results($result, $source);
+ $collection[$suite_name]['tests'][] = $result;
+ $collection[$suite_name]['status'] = $this->_get_suite_status(
+ $result['status'], $collection[$suite_name]['status']
+ );
+ $collection[$suite_name]['time'] += $result['time'];
+ $statistics['tests'][$result['status']] += 1;
+ $statistics['tests']['total'] += 1;
+ }
+
+ foreach ( $collection as $suite ) {
+ $statistics['suites'][$suite['status']] += 1;
+ $statistics['suites']['total'] += 1;
+ }
+
+ $final = array(
+ 'suites' => $collection,
+ 'stats' => $this->_add_percentages($statistics)
+ );
+
+ return $final;
+ }
+
+ /**
+ * Converts the first nested layer of PHPUnit-generated JSON to an
+ * associative array.
+ *
+ * @param string $str The JSON output from PHPUnit.
+ * @access protected
+ * @return array
+ */
+ protected function _convert_json($str) {
+ $str = str_replace('"', '"', $str);
+
+ $tags = array();
+ $nest = 0;
+ $start_mark = 0;
+ $in_quotes = false;
+
+ $length = strlen($str);
+ for ( $i = 0; $i < $length; $i++ ) {
+ $char = $str{$i};
+
+ if ( $char == '"' ) {
+ // Escaped quote in debug output
+ if ( !$in_quotes || $str{$i - 1} == "\\" ) {
+ $i = strpos($str, '"', $i + 1) - 1;
+ $in_quotes = true;
+ } else {
+ $in_quotes = false;
+ }
+ continue;
+ }
+
+ if ( $char == '{' ) {
+ $nest++;
+ if ( $nest == 1 ) {
+ $start_mark = $i;
+ }
+ } elseif ( $char == '}' && $nest > 0 ) {
+ if ( $nest == 1 ) {
+ $tags[] = substr(
+ $str, $start_mark + 1, $i - $start_mark - 1
+ );
+ $start_mark = $i;
+ }
+ $nest--;
+ }
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Normalizes the test results.
+ *
+ * @param array $test_results The parsed test results.
+ * @param string $source The executing source (web or cli).
+ * @access protected
+ * @return string
+ */
+ protected function _format_test_results($test_results, $source) {
+ $status = $this->_get_test_status(
+ $test_results['status'], $test_results['message']
+ );
+ $name = substr(
+ $test_results['test'], strpos($test_results['test'], '::') + 2
+ );
+ $time = $test_results['time'];
+ $message = $test_results['message'];
+ $output = ( isset($test_results['output']) )
+ ? trim($test_results['output'])
+ : '';
+ $trace = $this->_get_trace($test_results['trace'], $source);
+
+ return compact(
+ 'status',
+ 'name',
+ 'time',
+ 'message',
+ 'output',
+ 'trace'
+ );
+ }
+
+ /**
+ * Returns the errors collected by the custom error handler.
+ *
+ * @access public
+ * @return array
+ */
+ public function get_errors() {
+ return $this->_errors;
+ }
+
+ /**
+ * Determines the overall suite status based on the current status
+ * of the suite and the status of a single test.
+ *
+ * @param string $test_status The status of the test.
+ * @param string $suite_status The current status of the suite.
+ * @access protected
+ * @return string
+ */
+ protected function _get_suite_status($test_status, $suite_status) {
+ if (
+ $test_status === 'incomplete' && $suite_status !== 'failed'
+ && $suite_status !== 'skipped'
+ ) {
+ return 'incomplete';
+ }
+ if ( $test_status === 'skipped' && $suite_status !== 'failed' ) {
+ return 'skipped';
+ }
+ if ( $test_status === 'failed' ) {
+ return 'failed';
+ }
+ return $suite_status;
+ }
+
+ /**
+ * Retrieves the status from a PHPUnit test result.
+ *
+ * @param string $status The status supplied by VPU's transformed JSON.
+ * @param string $message The message supplied by VPU's transformed JSON.
+ * @access protected
+ * @return string
+ */
+ protected function _get_test_status($status, $message) {
+ switch ( $status ) {
+ case 'pass':
+ return 'succeeded';
+ case 'error':
+ if ( stripos($message, 'skipped') !== false ) {
+ return 'skipped';
+ }
+ if ( stripos($message, 'incomplete') !== false ) {
+ return 'incomplete';
+ }
+ return 'failed';
+ case 'fail':
+ return 'failed';
+ default:
+ return '';
+ }
+ }
+
+ /**
+ * Filters the stack trace from a PHPUnit test result to exclude VPU's
+ * trace.
+ *
+ * @param string $stack The stack trace.
+ * @param string $source The executing source (web or cli).
+ * @access protected
+ * @return string
+ */
+ protected function _get_trace($stack, $source) {
+ if ( !$stack ) {
+ return '';
+ }
+
+ ob_start();
+ if ( $source == 'web' ) {
+ print_r(array_slice($stack, 0, -6));
+ } else {
+ print_r(array_slice($stack, 0, -2));
+ }
+ $trace = trim(ob_get_contents());
+ ob_end_clean();
+
+ return $trace;
+ }
+
+ /**
+ * Serves as the error handler.
+ *
+ * @param int $number The level of the error raised.
+ * @param string $message The error message.
+ * @param string $file The file in which the error was raised.
+ * @param int $line The line number at which the error was raised.
+ * @access public
+ * @return bool
+ */
+ public function handle_errors($number, $message, $file, $line) {
+ if ( $number > error_reporting() ) {
+ return true;
+ }
+
+ switch ( $number ) {
+ case E_WARNING:
+ $type = 'E_WARNING';
+ break;
+ case E_NOTICE:
+ $type = 'E_NOTICE';
+ break;
+ case E_USER_ERROR:
+ $type = 'E_USER_ERROR';
+ break;
+ case E_USER_WARNING:
+ $type = 'E_USER_WARNING';
+ break;
+ case E_USER_NOTICE:
+ $type = 'E_USER_NOTICE';
+ break;
+ case E_STRICT:
+ $type = 'E_STRICT';
+ break;
+ case E_RECOVERABLE_ERROR:
+ $type = 'E_RECOVERABLE_ERROR';
+ break;
+ case E_DEPRECATED:
+ $type = 'E_DEPRECATED';
+ break;
+ case E_USER_DEPRECATED:
+ $type = 'E_USER_DEPRECATED';
+ break;
+ default:
+ $type = 'Unknown';
+ break;
+ }
+ $this->_errors[] = compact('type', 'message', 'file', 'line');
+ return true;
+ }
+
+ /**
+ * Parses and formats the JSON output from PHPUnit into an associative array.
+ *
+ * @param string $pu_output The JSON output from PHPUnit.
+ * @access protected
+ * @return array
+ */
+ protected function _parse_output($pu_output) {
+ $results = '';
+ foreach ( $this->_convert_json($pu_output) as $elem ) {
+ $elem = '{' . $elem . '}';
+ $pos = strpos($pu_output, $elem);
+ $pu_output = substr_replace($pu_output, '|||', $pos, strlen($elem));
+ $results .= $elem . ',';
+ }
+
+ $results = '[' . rtrim($results, ',') . ']';
+
+ $results = json_decode($results, true);
+
+ // For PHPUnit 3.5.x, which doesn't include test output in the JSON
+ $pu_output = explode('|||', $pu_output);
+ foreach ( $pu_output as $key => $data ) {
+ if ( $data ) {
+ $results[$key]['output'] = $data;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Retrieves the files from any supplied directories, and filters
+ * the list of tests by ensuring that the files exist and are PHP files.
+ *
+ * @param array $tests The directories/filenames containing the tests to
+ * be run through PHPUnit.
+ * @access protected
+ * @return array
+ */
+ protected function _parse_tests($tests) {
+ $collection = array();
+
+ foreach ( $tests as $test ) {
+ if ( is_dir($test) ) {
+ $it = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator(realpath($test)),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+ while ( $it->valid() ) {
+ $ext = strtolower(pathinfo($it->key(), PATHINFO_EXTENSION));
+ if ( !$it->isDot() && $ext == 'php' ) {
+ $collection[] = $it->key();
+ }
+
+ $it->next();
+ }
+ continue;
+ }
+
+ $ext = strtolower(pathinfo($test, PATHINFO_EXTENSION));
+ if ( file_exists($test) && $ext == 'php' ) {
+ $collection[] = realpath($test);
+ }
+ }
+ // Avoid returning duplicates
+ return array_keys(array_flip($collection));
+ }
+
+ /**
+ * Runs supplied tests through PHPUnit.
+ *
+ * @param array $tests The directories/filenames containing the tests
+ * to be run through PHPUnit.
+ * @access public
+ * @return string
+ */
+ public function run_tests($tests) {
+ $suite = new \PHPUnit_Framework_TestSuite();
+
+ $tests = $this->_parse_tests($tests);
+ $original_classes = get_declared_classes();
+ foreach ( $tests as $test ) {
+ require $test;
+ }
+ $new_classes = get_declared_classes();
+ $tests = array_diff($new_classes, $original_classes);
+ foreach ( $tests as $test ) {
+ if ( is_subclass_of($test, 'PHPUnit_Framework_TestCase') ) {
+ $suite->addTestSuite($test);
+ }
+ }
+
+ $result = new \PHPUnit_Framework_TestResult();
+ $result->addListener(new \PHPUnit_Util_Log_JSON());
+
+ // We need to temporarily turn off html_errors to ensure correct
+ // parsing of test debug output
+ $html_errors = ini_get('html_errors');
+ ini_set('html_errors', 0);
+
+ ob_start();
+ $suite->run($result);
+ $results = ob_get_contents();
+ ob_end_clean();
+
+ ini_set('html_errors', $html_errors);
+ return $results;
+ }
+
+ /**
+ * Checks that the provided XML configuration file contains the necessary
+ * JSON listener.
+ *
+ * @param string $xml_config The path to the PHPUnit XML configuration
+ * file.
+ * @access protected
+ * @return void
+ */
+ protected function _check_xml_configuration($xml_config) {
+ $configuration = \PHPUnit_Util_Configuration::getInstance($xml_config);
+ $listeners = $configuration->getListenerConfiguration();
+
+ $required_listener = 'PHPUnit_Util_Log_JSON';
+ $found = false;
+ foreach ( $listeners as $listener ) {
+ if ( $listener['class'] === $required_listener ) {
+ $found = true;
+ break;
+ }
+ }
+ if ( !$found ) {
+ throw new \DomainException(
+ "XML Configuration file doesn't contain the required " .
+ "{$required_listener} listener."
+ );
+ }
+ }
+
+ /**
+ * Runs PHPUnit with the supplied XML configuration file.
+ *
+ * @param string $xml_config The path to the PHPUnit XML configuration
+ * file.
+ * @access public
+ * @return string
+ */
+ public function run_with_xml($xml_config) {
+ $this->_check_xml_configuration($xml_config);
+ $command = new \PHPUnit_TextUI_Command();
+
+ // We need to temporarily turn off html_errors to ensure correct
+ // parsing of test debug output
+ $html_errors = ini_get('html_errors');
+ ini_set('html_errors', 0);
+
+ ob_start();
+ $command->run(array('--configuration', $xml_config, '--stderr'), false);
+ $results = ob_get_contents();
+ ob_end_clean();
+
+ ini_set('html_errors', $html_errors);
+
+ $start = strpos($results, '{');
+ $end = strrpos($results, '}');
+ return substr($results, $start, $end - $start + 1);
+ }
+
+}
+
+?>
diff --git a/vpu/app/public/.htaccess b/vpu/app/public/.htaccess
new file mode 100644
index 0000000..c68f87a
--- /dev/null
+++ b/vpu/app/public/.htaccess
@@ -0,0 +1,8 @@
++ VisualPHPUnit was built and is actively maintained by Nick Sinopoli. It is offered under the BSD License. +
++ The following tools were indispensable during development: +
++ Current stable release is v2.2, last updated on May 11, 2013. +
++ Bug reports and pull requests are welcomed on the project's issue tracker. +
++ If you encounter any issues, you may contact NSinopoli@gmail.com for support. +
+
+ * $routes = array(
+ * array(
+ * mixed $request_method, string $request_uri, callable $callback
+ * ),
+ * ...
+ * );
+ *
+ *
+ * where:
+ *
+ *
+ * $request_method can be a string ('GET', 'POST', 'PUT', 'DELETE'),
+ * or an array (e.g., array('GET, 'POST')). Note that $request_method
+ * is case-insensitive.
+ *
+ *
+ *
+ * $request_uri is a string, with optional match types. Valid match types
+ * are as follows:
+ *
+ * [i] - integer
+ * [a] - alphanumeric
+ * [h] - hexadecimal
+ * [*] - anything
+ *
+ * Match types can be combined with parameter names, which will be
+ * captured:
+ *
+ * [i:id] - will match an integer, storing it within the returned 'params'
+ * array under the 'id' key
+ * [a:name] - will match an alphanumeric value, storing it within the
+ * returned 'params' array under the 'name' key
+ *
+ * Here are some examples to help illustrate:
+ *
+ * /post/[i:id] - will match on /post/32 (with the returned 'params' array
+ * containing an 'id' key with a value of 32), but will not match on
+ * /post/today
+ *
+ * /find/[h:serial] - will match on /find/ae32 (with the returned 'params'
+ * array containing a 'serial' key will a value of 'ae32'), but will not
+ * match on /find/john
+ *
+ *
+ *
+ * $callback is a valid callback function.
+ *
+ *
+ * Returns an array containing the following keys:
+ *
+ * * 'params' - The parameters collected from the matched uri
+ * * 'callback' - The callback function pulled from the matched route
+ *
+ * @param string $request_uri The request uri.
+ * @param string $request_method The request method.
+ * @param array $routes The routes.
+ * @return array
+ */
+ public function parse($request_uri, $request_method, $routes) {
+ foreach ( $routes as $route ) {
+ list($method, $uri, $callback) = $route;
+
+ if ( is_array($method) ) {
+ $found = false;
+ foreach ( $method as $value ) {
+ if ( strcasecmp($request_method, $value) == 0 ) {
+ $found = true;
+ break;
+ }
+ }
+ if ( !$found ) {
+ continue;
+ }
+ } elseif ( strcasecmp($request_method, $method) != 0 ) {
+ continue;
+ }
+
+ if ( is_null($uri) || $uri == '*' ) {
+ $params = array();
+ return compact('params', 'callback');
+ }
+
+ $route_to_match = '';
+ $len = strlen($uri);
+
+ for ( $i = 0; $i < $len; $i++ ) {
+ $char = $uri[$i];
+ $is_regex = (
+ $char == '[' || $char == '(' || $char == '.'
+ || $char == '?' || $char == '+' || $char == '{'
+ );
+ if ( $is_regex ) {
+ $route_to_match = $uri;
+ break;
+ } elseif (
+ !isset($request_uri[$i]) || $char != $request_uri[$i]
+ ) {
+ continue 2;
+ }
+ $route_to_match .= $char;
+ }
+
+ $regex = $this->_compile_regex($route_to_match);
+ if ( preg_match($regex, $request_uri, $params) ) {
+ foreach ( $params as $key => $arg ) {
+ if ( is_numeric($key) ) {
+ unset($params[$key]);
+ }
+ }
+ return compact('params', 'callback');
+ }
+ }
+ return array(
+ 'params' => null,
+ 'callback' => null
+ );
+ }
+
+}
+
+?>