├── .github └── workflows │ └── php.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── src └── Redis │ ├── Graph.php │ └── Graph │ ├── Edge.php │ ├── Node.php │ └── Query │ └── Result.php └── tests ├── 01_connection.phpt ├── 02_delete.phpt ├── 03_node_create.phpt ├── 04_node_delete.phpt ├── 05_edge_create.phpt ├── 06_edge_traversal.phpt └── run.php /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | services: 11 | redisgraph: 12 | # image: redislabs/redisgraph:latest 13 | image: redislabs/redisgraph:2.0.21 14 | ports: 15 | - 6379:6379 16 | 17 | steps: 18 | - uses: actions/checkout@v1 19 | 20 | - name: Validate composer.json and composer.lock 21 | run: composer validate 22 | 23 | - name: Install dependencies 24 | run: composer install --prefer-dist --no-progress --no-suggest 25 | 26 | - name: Run test suite 27 | run: composer run-script test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | - 7.2 7 | 8 | services: 9 | - docker 10 | 11 | before_script: 12 | - export TEST_PHP_EXECUTABLE=`which php` 13 | - curl http://getcomposer.org/installer | php 14 | - php composer.phar install 15 | - php composer.phar require predis/predis 16 | - docker run -d -p 6379:6379 -it --rm --name redisgraph redislabs/redisgraph 17 | 18 | script: 19 | - php ./tests/run.php | tee test-output.txt && grep 'TEST SUMMARY$' test-output.txt > /dev/null ; test $? '!=' 0 20 | 21 | after_failure: 22 | - for FILE in `find ./tests -name '*.diff'`; do echo echo $FILE; cat $FILE; echo; done 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 kjdev 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RedisGraph PHP Client 2 | ===================== 3 | 4 | [![Build Status](https://travis-ci.org/kjdev/php-redis-graph.svg?branch=master)](https://travis-ci.org/kjdev/php-redis-graph) 5 | 6 | [RedisGraph](https://github.com/RedisGraph/RedisGraph) 7 | 8 | Install 9 | ------- 10 | 11 | ``` sh 12 | composer require kjdev/redis-graph 13 | ``` 14 | 15 | As Redis's client library, use either. 16 | 17 | - `predis/predis` 18 | 19 | > `composer require predis/predis` 20 | 21 | - `ext-redis` 22 | 23 | > `pecl install redis` 24 | 25 | 26 | Example 27 | ------- 28 | 29 | ``` php 30 | require __DIR__ . '/vendor/autoload.php'; 31 | 32 | use Redis\Graph; 33 | use Redis\Graph\Node; 34 | use Redis\Graph\Edge; 35 | 36 | $redis = new Predis\Client('redis://127.0.0.1:6379/'); 37 | // OR 38 | // $redis = new Redis(); 39 | // $redis->connect('127.0.0.1', 6379); 40 | 41 | $graph = new Graph('social', $redis); 42 | 43 | $john = new Node('person', [ 44 | 'name' => 'John Doe', 45 | 'age' => 33, 46 | 'gender' => 'male', 47 | 'status' => 'single' 48 | ]); 49 | $graph->addNode($john); 50 | 51 | $japan = new Node('country', [ 52 | 'name' => 'Japan' 53 | ]); 54 | $graph->addNode($japan); 55 | 56 | $edge = new Edge($john, $japan, 'visited', ['purpose' => 'pleasure']); 57 | $graph->addEdge($edge); 58 | 59 | $graph->commit(); 60 | 61 | $query = 'MATCH (p:person)-[v:visited {purpose:"pleasure"}]->(c:country) RETURN p.name, p.age, v.purpose, c.name'; 62 | 63 | $result = $graph->query($query); 64 | 65 | // Print resultset 66 | $result->prettyPrint(); 67 | 68 | // Iterate through resultset 69 | while ($row = $result->fetch()) { 70 | var_dump($row); 71 | } 72 | // var_dump($result->fetchAll()); 73 | 74 | // All done, remove graph. 75 | $graph->delete(); 76 | ``` 77 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kjdev/redis-graph", 3 | "type": "library", 4 | "description": "PHP RedisGraph client", 5 | "keywords": ["redis"], 6 | "license": "MIT", 7 | "homepage": "https://github.com/kjdev/php-redis-graph", 8 | "suggest": { 9 | "ext-redis": "If you want to use redis extension.", 10 | "predis/predis": "If you want to use predis." 11 | }, 12 | "scripts": { 13 | "test": "phpunit tests" 14 | }, 15 | "require": { 16 | "php": ">=7.0", 17 | "predis/predis": "^1.1" 18 | }, 19 | "autoload": { 20 | "psr-4": { "Redis\\": "src/Redis/" } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Redis/Graph.php: -------------------------------------------------------------------------------- 1 | client = get_class($redis); 31 | if (!in_array($this->client, [self::CLIENT_REDIS, self::CLIENT_PREDIS], true)) { 32 | throw new RuntimeException('Unsupported Redis client.'); 33 | } 34 | 35 | $this->name = $name; 36 | $this->redis = $redis; 37 | 38 | $response = $this->redisCommand('MODULE', 'LIST'); 39 | if (!isset($response[0]) || !is_array($response[0]) 40 | || !in_array('graph', $response[0], true)) { 41 | throw new RuntimeException('RedisGraph module not loaded.'); 42 | } 43 | } 44 | 45 | public function addNode(Node $node): self 46 | { 47 | $this->nodes[] = $node; 48 | return $this; 49 | } 50 | 51 | public function getNode(int $var): Node 52 | { 53 | if (isset($this->nodes[$var])) { 54 | return $this->nodes[$var]; 55 | } 56 | 57 | $label = $this->getLabel($var); 58 | return new Node(":{$label}"); 59 | } 60 | 61 | public function addEdge(Edge $edge): self 62 | { 63 | assert(in_array($edge->src, $this->nodes, true)); 64 | assert(in_array($edge->dest, $this->nodes, true)); 65 | $this->edges[] = $edge; 66 | return $this; 67 | } 68 | 69 | public function commit(): Result 70 | { 71 | $query = 'CREATE '; 72 | foreach ($this->nodes as $node) { 73 | $query .= (string)$node . ','; 74 | } 75 | foreach ($this->edges as $edge) { 76 | $query .= (string)$edge . ','; 77 | } 78 | 79 | // Discard leading comma. 80 | $query = rtrim($query, ','); 81 | 82 | return $this->query($query); 83 | } 84 | 85 | public function query($command): Result 86 | { 87 | $response = $this->redisCommand( 88 | 'GRAPH.QUERY', 89 | $this->name, 90 | $command, 91 | '--compact' 92 | ); 93 | return new Result($this, $response); 94 | } 95 | 96 | public function explain($query): string 97 | { 98 | return implode( 99 | PHP_EOL, 100 | $this->redisCommand('GRAPH.EXPLAIN', $this->name, $query) 101 | ); 102 | } 103 | 104 | public function delete() 105 | { 106 | return $this->redisCommand('GRAPH.DELETE', $this->name); 107 | } 108 | 109 | private function redisCommand() 110 | { 111 | switch ($this->client) { 112 | case self::CLIENT_REDIS: 113 | return call_user_func_array( 114 | [$this->redis, 'rawCommand'], 115 | func_get_args() 116 | ); 117 | case self::CLIENT_PREDIS: 118 | return $this->redis->executeRaw(func_get_args()); 119 | default: 120 | throw new RuntimeException('Unknown Redis client.'); 121 | } 122 | } 123 | 124 | private function call(string $procedure): array 125 | { 126 | $response = []; 127 | 128 | $result = $this->query("CALL {$procedure}"); 129 | foreach ($result->values as $var) { 130 | $response[] = current($var); 131 | } 132 | 133 | return $response; 134 | } 135 | 136 | public function getLabel(int $var): string 137 | { 138 | if (count($this->labels) === 0) { 139 | $this->labels = $this->call('db.labels()'); 140 | } 141 | return $this->labels[$var] ?? ''; 142 | } 143 | 144 | public function getProperty(int $var): string 145 | { 146 | if (count($this->properties) === 0) { 147 | $this->properties = $this->call('db.propertyKeys()'); 148 | } 149 | return $this->properties[$var] ?? ''; 150 | } 151 | 152 | public function getRelation(int $var): string 153 | { 154 | if (count($this->relations) === 0) { 155 | $this->relations = $this->call('db.relationshipTypes()'); 156 | } 157 | return $this->relations[$var] ?? ''; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Redis/Graph/Edge.php: -------------------------------------------------------------------------------- 1 | src = $src; 18 | $this->dest = $dest; 19 | $this->relation = $relation; 20 | $this->properties = $properties; 21 | } 22 | 23 | public function __toString() 24 | { 25 | // Source node. 26 | $res = '(' . $this->src->alias . ')'; 27 | 28 | // Edge 29 | $res .= '-['; 30 | if ($this->relation) { 31 | $res .= ':' . $this->relation; 32 | } 33 | if ($this->properties) { 34 | $props = []; 35 | foreach ($this->properties as $key => $val) { 36 | if (is_int($val) || is_double($val)) { 37 | $props[] = $key . ':' . $val; 38 | } else { 39 | $props[] = $key . ':"' . trim((string)$val, '"') . '"'; 40 | } 41 | } 42 | $res .= '{' . implode(',', $props) . '}'; 43 | } 44 | $res .= ']->'; 45 | 46 | // Dest node. 47 | $res .= '(' . $this->dest->alias . ')'; 48 | 49 | return $res; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Redis/Graph/Node.php: -------------------------------------------------------------------------------- 1 | alias = $names[0]; 15 | $this->label = $names[1]; 16 | } else { 17 | $this->alias = $this->randomString(); 18 | $this->label = $names[0]; 19 | } 20 | 21 | $this->properties = $properties; 22 | } 23 | 24 | private function randomString($length = 10) 25 | { 26 | return substr( 27 | str_shuffle(str_repeat( 28 | 'abcdefghijklmnopqrstuvwxyz', $length 29 | )), 30 | 0, 31 | $length 32 | ); 33 | } 34 | 35 | public function __toString() 36 | { 37 | $res = '('; 38 | if ($this->alias) { 39 | $res .= $this->alias; 40 | } 41 | if ($this->label) { 42 | $res .= ':' . $this->label; 43 | } 44 | if ($this->properties) { 45 | $props = []; 46 | foreach ($this->properties as $key => $val) { 47 | if (is_int($val) || is_double($val)) { 48 | $props[] = $key . ':' . $val; 49 | } else { 50 | $props[] = $key . ':"' . trim((string)$val, '"') . '"'; 51 | } 52 | } 53 | $res .= '{' . implode(',', $props) . '}'; 54 | } 55 | $res .= ')'; 56 | 57 | return $res; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Redis/Graph/Query/Result.php: -------------------------------------------------------------------------------- 1 | graph = $graph; 35 | 36 | if (count($response) === 1) { 37 | $this->parseStatistics($response[0]); 38 | } else { 39 | $this->parseResults($response); 40 | $this->parseStatistics(end($response)); 41 | } 42 | } 43 | 44 | public function stats(string $key = '') 45 | { 46 | if ($key === '') { 47 | return $this->stats; 48 | } 49 | if (array_key_exists($key, $this->stats)) { 50 | return $this->stats[$key]; 51 | } 52 | return false; 53 | } 54 | 55 | public function fetch() 56 | { 57 | if (count($this->values) <= $this->count) { 58 | return false; 59 | } 60 | $this->count++; 61 | return $this->values[($this->count - 1)]; 62 | } 63 | 64 | public function fetchAll(): array 65 | { 66 | return $this->values; 67 | } 68 | 69 | public function prettyPrint() 70 | { 71 | if (count($this->values) === 0) { 72 | return; 73 | } 74 | 75 | $length = []; 76 | foreach ($this->values as $value) { 77 | foreach ($value as $key => $val) { 78 | if (is_scalar($val)) { 79 | $length[$key] = max(strlen($key), strlen((string) $val)); 80 | } elseif ($val instanceof Node) { 81 | $length[$key] = max( 82 | strlen($key), 83 | strlen(get_class($val) . "@{$val->label}") 84 | ); 85 | } elseif ($val instanceof Edge) { 86 | $length[$key] = max( 87 | strlen($key), 88 | strlen(get_class($val) . '@' . (string) $val->relation) 89 | ); 90 | } elseif (is_object($val)) { 91 | $length[$key] = max(strlen($key), strlen(get_class($val))); 92 | } else { 93 | $length[$key] = strlen($key); 94 | } 95 | } 96 | } 97 | 98 | $line = function () use ($length) { 99 | foreach ($length as $len) { 100 | echo '+', str_repeat('-', $len + 2); 101 | } 102 | echo '+', PHP_EOL; 103 | }; 104 | $line(); 105 | 106 | foreach ($length as $key => $val) { 107 | echo '| ', str_pad($key, $val), ' '; 108 | } 109 | echo '|', PHP_EOL; 110 | $line(); 111 | 112 | foreach ($this->values as $value) { 113 | foreach ($value as $key => $val) { 114 | if (is_scalar($val) || is_null($val)) { 115 | echo '| ', str_pad((string) $val, $length[$key]), ' '; 116 | } elseif ($val instanceof Node) { 117 | echo '| ', 118 | str_pad(get_class($val) . "@{$val->label}", $length[$key]), 119 | ' '; 120 | } elseif ($val instanceof Edge) { 121 | echo '| ', 122 | str_pad( 123 | get_class($val) . '@' . (string) $val->relation, 124 | $length[$key] 125 | ), 126 | ' '; 127 | } elseif (is_object($val)) { 128 | echo '| ', str_pad(get_class($val), $length[$key]), ' '; 129 | } else { 130 | echo '| ', str_pad('*', $length[$key]), ' '; 131 | } 132 | } 133 | echo '|', PHP_EOL; 134 | } 135 | $line(); 136 | } 137 | 138 | private function parseStatistics(array $response) 139 | { 140 | foreach ($response as $line) { 141 | if (preg_match('/^Labels added:(.*)$/', $line, $matches)) { 142 | $this->stats['labels_added'] = (int)(trim($matches[1])); 143 | } elseif (preg_match('/^Nodes created:(.*)$/', $line, $matches)) { 144 | $this->stats['nodes_created'] = (int)(trim($matches[1])); 145 | } elseif (preg_match('/^Nodes deleted:(.*)$/', $line, $matches)) { 146 | $this->stats['nodes_deleted'] = (int)(trim($matches[1])); 147 | } elseif (preg_match('/^Relationships deleted:(.*)$/', $line, $matches)) { 148 | $this->stats['relationships_deleted'] = (int)(trim($matches[1])); 149 | } elseif (preg_match('/^Properties set:(.*)$/', $line, $matches)) { 150 | $this->stats['properties_set'] = (int)(trim($matches[1])); 151 | } elseif (preg_match('/^Relationships created:(.*)$/', $line, $matches)) { 152 | $this->stats['relationships_created'] = (int)(trim($matches[1])); 153 | } elseif (preg_match( 154 | '/^Query internal execution time: *([0-9\.]*) .*$/', $line, $matches) 155 | ) { 156 | $this->stats['internal_execution_time'] = (double)(trim($matches[1])); 157 | } 158 | } 159 | } 160 | 161 | private function parseResults(array $response) 162 | { 163 | $this->headers = $this->parseHeader($response); 164 | 165 | // Empty header. 166 | if (count($this->headers) === 0) { 167 | return; 168 | } 169 | 170 | $keys = []; 171 | foreach ($this->headers as $value) { 172 | $keys[] = $value[1] ?? ''; 173 | } 174 | 175 | $records = $this->parseRecords($response); 176 | 177 | foreach ($records as $record) { 178 | $this->values[] = array_combine($keys, $record); 179 | } 180 | } 181 | 182 | private function parseHeader(array $response): array 183 | { 184 | // An array of column name/column type pairs. 185 | return $response[0]; 186 | } 187 | 188 | private function parseRecords(array $response): array 189 | { 190 | $records = []; 191 | 192 | foreach ($response[1] as $row) { 193 | $record = []; 194 | foreach ($row as $i => $var) { 195 | switch ($this->headers[$i][0] ?? self::COLUMN_UNKNOWN) { 196 | case self::COLUMN_SCALAR: 197 | $record[] = $this->parseScalar($var); 198 | break; 199 | case self::COLUMN_NODE: 200 | $record[] = $this->parseNode($var); 201 | break; 202 | case self::COLUMN_RELATION: 203 | $record[] = $this->parseEdge($var); 204 | break; 205 | default: 206 | trigger_error('Unknown column type.', E_USER_WARNING); 207 | } 208 | } 209 | $records[] = $record; 210 | } 211 | 212 | return $records; 213 | } 214 | 215 | private function parseScalar(array $var) 216 | { 217 | $type = (int) $var[0]; 218 | $value = $var[1]; 219 | 220 | switch ($type) { 221 | case self::PROPERTY_NULL: 222 | $scalar = null; 223 | break; 224 | case self::PROPERTY_STRING: 225 | $scalar = (string) $value; 226 | break; 227 | case self::PROPERTY_INTEGER: 228 | $scalar = (int) $value; 229 | break; 230 | case self::PROPERTY_BOOLEAN: 231 | $scalar = (bool) $value; 232 | break; 233 | case self::PROPERTY_DOUBLE: 234 | $scalar = (float) $value; 235 | break; 236 | case self::PROPERTY_UNKNOWN: 237 | trigger_error('Unknown scalar type.', E_USER_WARNING); 238 | } 239 | 240 | return $scalar ?? null; 241 | } 242 | 243 | private function parseNode(array $var): Node 244 | { 245 | // Node ID (integer), 246 | // [label string offset (integer)], 247 | // [[name, value type, value] X N] 248 | 249 | $id = (int) $var[0]; 250 | 251 | $label = ''; 252 | if (count($var[1]) !== 0) { 253 | $label = $this->graph->getLabel($var[1][0]); 254 | } 255 | 256 | $properties = $this->parseEntityProperties($var[2]); 257 | 258 | return new Node(":{$label}", $properties); 259 | } 260 | 261 | private function parseEntityProperties(array $props): array 262 | { 263 | // [[name, value type, value] X N] 264 | 265 | $properties = []; 266 | 267 | foreach ($props as $prop) { 268 | $name = $this->graph->getProperty($prop[0]); 269 | $value = $this->parseScalar(array_slice($prop, 1)); 270 | $properties[$name] = $value; 271 | } 272 | 273 | return $properties; 274 | } 275 | 276 | private function parseEdge(array $var): Edge 277 | { 278 | // Edge ID (integer), 279 | // reltype string offset (integer), 280 | // src node ID offset (integer), 281 | // dest node ID offset (integer), 282 | // [[name, value, value type] X N] 283 | 284 | $id = (int) $var[0]; 285 | $relation = $this->graph->getRelation($var[1]); 286 | $src = $this->graph->getNode($var[2]); 287 | $dest = $this->graph->getNode($var[3]); 288 | $properties = $this->parseEntityProperties($var[4]); 289 | 290 | return new Edge($src, $dest, $relation, $properties); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /tests/01_connection.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test connection 3 | --SKIPIF-- 4 | --FILE-- 5 | 'src', 16 | ]); 17 | $graph->addNode($node); 18 | $result = $graph->commit(); 19 | 20 | echo $graph->delete(), PHP_EOL; 21 | --EXPECTF-- 22 | Graph removed, internal execution time: %f milliseconds 23 | -------------------------------------------------------------------------------- /tests/03_node_create.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test node create 3 | --SKIPIF-- 4 | --FILE-- 5 | 'src', 17 | ]); 18 | echo $node, PHP_EOL; 19 | 20 | $graph->addNode($node); 21 | 22 | $query = 'CREATE '; 23 | foreach ($graph->nodes as $node) { 24 | $query .= (string)$node . ','; 25 | } 26 | $query = rtrim($query, ','); 27 | echo $query, PHP_EOL; 28 | 29 | $result = $graph->commit(); 30 | echo get_class($result), PHP_EOL; 31 | 32 | var_dump($result->stats()); 33 | 34 | echo $graph->explain($query); 35 | 36 | $graph->delete(); 37 | --EXPECTF-- 38 | (%s:node{name:"src"}) 39 | CREATE (%s:node{name:"src"}) 40 | Redis\Graph\Query\Result 41 | array(4) { 42 | ["labels_added"]=> 43 | int(1) 44 | ["nodes_created"]=> 45 | int(1) 46 | ["properties_set"]=> 47 | int(1) 48 | ["internal_execution_time"]=> 49 | float(%f) 50 | } 51 | Create 52 | -------------------------------------------------------------------------------- /tests/04_node_delete.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test node delete 3 | --SKIPIF-- 4 | --FILE-- 5 | 'src', 17 | ]); 18 | $graph->addNode($node); 19 | $graph->commit(); 20 | 21 | $query = "MATCH (t:node) WHERE t.name = 'src' RETURN t"; 22 | $result = $graph->query($query); 23 | $result->prettyPrint(); 24 | var_dump($result->fetchAll()); 25 | 26 | // DELETE 27 | echo 'DELETE', PHP_EOL; 28 | $query = "MATCH (t:node) WHERE t.name = 'src' DELETE t"; 29 | $result = $graph->query($query); 30 | $result->prettyPrint(); 31 | var_dump($result->fetchAll()); 32 | var_dump($result->stats()); 33 | 34 | $query = "MATCH (t:node) WHERE t.name = 'src' RETURN t"; 35 | $result = $graph->query($query); 36 | $result->prettyPrint(); 37 | var_dump($result->fetchAll()); 38 | 39 | $graph->delete(); 40 | --EXPECTF-- 41 | +-----------------------+ 42 | | t | 43 | +-----------------------+ 44 | | Redis\Graph\Node@node | 45 | +-----------------------+ 46 | array(1) { 47 | [0]=> 48 | array(1) { 49 | ["t"]=> 50 | object(Redis\Graph\Node)#%d (3) { 51 | ["label"]=> 52 | string(4) "node" 53 | ["alias"]=> 54 | string(0) "" 55 | ["properties"]=> 56 | array(1) { 57 | ["name"]=> 58 | string(3) "src" 59 | } 60 | } 61 | } 62 | } 63 | DELETE 64 | array(0) { 65 | } 66 | array(2) { 67 | ["nodes_deleted"]=> 68 | int(1) 69 | ["internal_execution_time"]=> 70 | float(%f) 71 | } 72 | array(0) { 73 | } 74 | -------------------------------------------------------------------------------- /tests/05_edge_create.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test edge create 3 | --SKIPIF-- 4 | --FILE-- 5 | 'src1', 18 | ]); 19 | $dest = new Node('node', [ 20 | 'name' => 'dest1', 21 | ]); 22 | $edge = new Edge($src, $dest, 'edge'); 23 | $graph->addNode($src); 24 | $graph->addNode($dest); 25 | $graph->addEdge($edge); 26 | 27 | $src = new Node('node', [ 28 | 'name' => 'src2', 29 | ]); 30 | $dest = new Node('node_type_2', [ 31 | 'name' => 'dest2', 32 | ]); 33 | $edge = new Edge($src, $dest, 'edge'); 34 | $graph->addNode($src); 35 | $graph->addNode($dest); 36 | $graph->addEdge($edge); 37 | 38 | $result = $graph->commit(); 39 | 40 | var_dump($result->stats()); 41 | 42 | $graph->delete(); 43 | --EXPECTF-- 44 | array(5) { 45 | ["labels_added"]=> 46 | int(2) 47 | ["nodes_created"]=> 48 | int(4) 49 | ["properties_set"]=> 50 | int(4) 51 | ["relationships_created"]=> 52 | int(2) 53 | ["internal_execution_time"]=> 54 | float(%f) 55 | } 56 | -------------------------------------------------------------------------------- /tests/06_edge_traversal.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test edge traversal 3 | --SKIPIF-- 4 | --FILE-- 5 | 'src11', 18 | ]); 19 | $dest = new Node('node', [ 20 | 'name' => 'dest11', 21 | ]); 22 | $edge = new Edge($src, $dest, 'edge'); 23 | $graph->addNode($src); 24 | $graph->addNode($dest); 25 | $graph->addEdge($edge); 26 | 27 | $src = new Node('node', [ 28 | 'name' => 'src12', 29 | ]); 30 | $dest = new Node('node', [ 31 | 'name' => 'dest12', 32 | ]); 33 | $edge = new Edge($src, $dest, 'edge'); 34 | $graph->addNode($src); 35 | $graph->addNode($dest); 36 | $graph->addEdge($edge); 37 | 38 | $src = new Node('node', [ 39 | 'name' => 'src2', 40 | ]); 41 | $dest = new Node('node_type_2', [ 42 | 'name' => 'dest2', 43 | ]); 44 | $edge = new Edge($src, $dest, 'edge'); 45 | $graph->addNode($src); 46 | $graph->addNode($dest); 47 | $graph->addEdge($edge); 48 | 49 | $graph->commit(); 50 | 51 | $query = 'MATCH (a)-[:edge]->(b:node) RETURN a, b'; 52 | $result = $graph->query($query); 53 | $result->prettyPrint(); 54 | while ($row = $result->fetch()) { 55 | var_dump($row); 56 | } 57 | 58 | $query = 'MATCH (s:node)-[:edge]->(d:node) WHERE s.name = "src11" RETURN s,d'; 59 | $result = $graph->query($query); 60 | $result->prettyPrint(); 61 | while ($row = $result->fetch()) { 62 | var_dump($row); 63 | } 64 | 65 | $query = 'MATCH (s:node)-[:edge]->(d:node) WHERE s.name = "src2" RETURN s,d'; 66 | $result = $graph->query($query); 67 | $result->prettyPrint(); 68 | while ($row = $result->fetch()) { 69 | var_dump($row); 70 | } 71 | 72 | $query = 'MATCH (s:node)-[:edge]->(d:node_type_2) WHERE s.name = "src2" RETURN s,d'; 73 | $result = $graph->query($query); 74 | $result->prettyPrint(); 75 | while ($row = $result->fetch()) { 76 | var_dump($row); 77 | } 78 | 79 | $graph->delete(); 80 | --EXPECTF-- 81 | +-----------------------+-----------------------+ 82 | | a | b | 83 | +-----------------------+-----------------------+ 84 | | Redis\Graph\Node@node | Redis\Graph\Node@node | 85 | | Redis\Graph\Node@node | Redis\Graph\Node@node | 86 | +-----------------------+-----------------------+ 87 | array(2) { 88 | ["a"]=> 89 | object(Redis\Graph\Node)#%d (3) { 90 | ["label"]=> 91 | string(4) "node" 92 | ["alias"]=> 93 | string(0) "" 94 | ["properties"]=> 95 | array(1) { 96 | ["name"]=> 97 | string(5) "src11" 98 | } 99 | } 100 | ["b"]=> 101 | object(Redis\Graph\Node)#%d (3) { 102 | ["label"]=> 103 | string(4) "node" 104 | ["alias"]=> 105 | string(0) "" 106 | ["properties"]=> 107 | array(1) { 108 | ["name"]=> 109 | string(6) "dest11" 110 | } 111 | } 112 | } 113 | array(2) { 114 | ["a"]=> 115 | object(Redis\Graph\Node)#%d (3) { 116 | ["label"]=> 117 | string(4) "node" 118 | ["alias"]=> 119 | string(0) "" 120 | ["properties"]=> 121 | array(1) { 122 | ["name"]=> 123 | string(5) "src12" 124 | } 125 | } 126 | ["b"]=> 127 | object(Redis\Graph\Node)#%d (3) { 128 | ["label"]=> 129 | string(4) "node" 130 | ["alias"]=> 131 | string(0) "" 132 | ["properties"]=> 133 | array(1) { 134 | ["name"]=> 135 | string(6) "dest12" 136 | } 137 | } 138 | } 139 | +-----------------------+-----------------------+ 140 | | s | d | 141 | +-----------------------+-----------------------+ 142 | | Redis\Graph\Node@node | Redis\Graph\Node@node | 143 | +-----------------------+-----------------------+ 144 | array(2) { 145 | ["s"]=> 146 | object(Redis\Graph\Node)#%d (3) { 147 | ["label"]=> 148 | string(4) "node" 149 | ["alias"]=> 150 | string(0) "" 151 | ["properties"]=> 152 | array(1) { 153 | ["name"]=> 154 | string(5) "src11" 155 | } 156 | } 157 | ["d"]=> 158 | object(Redis\Graph\Node)#%d (3) { 159 | ["label"]=> 160 | string(4) "node" 161 | ["alias"]=> 162 | string(0) "" 163 | ["properties"]=> 164 | array(1) { 165 | ["name"]=> 166 | string(6) "dest11" 167 | } 168 | } 169 | } 170 | +-----------------------+------------------------------+ 171 | | s | d | 172 | +-----------------------+------------------------------+ 173 | | Redis\Graph\Node@node | Redis\Graph\Node@node_type_2 | 174 | +-----------------------+------------------------------+ 175 | array(2) { 176 | ["s"]=> 177 | object(Redis\Graph\Node)#%d (3) { 178 | ["label"]=> 179 | string(4) "node" 180 | ["alias"]=> 181 | string(0) "" 182 | ["properties"]=> 183 | array(1) { 184 | ["name"]=> 185 | string(4) "src2" 186 | } 187 | } 188 | ["d"]=> 189 | object(Redis\Graph\Node)#%d (3) { 190 | ["label"]=> 191 | string(11) "node_type_2" 192 | ["alias"]=> 193 | string(0) "" 194 | ["properties"]=> 195 | array(1) { 196 | ["name"]=> 197 | string(5) "dest2" 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /tests/run.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | | 18 | | Preston L. Bannister | 19 | | Marcus Boerger | 20 | | Derick Rethans | 21 | | Sander Roobol | 22 | | (based on version by: Stig Bakken ) | 23 | | (based on the PHP 3 test framework by Rasmus Lerdorf) | 24 | +----------------------------------------------------------------------+ 25 | */ 26 | 27 | /* $Id: f4720f5b76a811a5f7ee1b19ce15e4c68ecf7307 $ */ 28 | 29 | /* Sanity check to ensure that pcre extension needed by this script is available. 30 | * In the event it is not, print a nice error message indicating that this script will 31 | * not run without it. 32 | */ 33 | 34 | if (!extension_loaded('pcre')) { 35 | echo <<'); 295 | $exts_to_test = explode(',',`$php $pass_options $info_params $no_file_cache "$info_file"`); 296 | // check for extensions that need special handling and regenerate 297 | $info_params_ex = array( 298 | 'session' => array('session.auto_start=0'), 299 | 'tidy' => array('tidy.clean_output=0'), 300 | 'zlib' => array('zlib.output_compression=Off'), 301 | 'xdebug' => array('xdebug.default_enable=0'), 302 | 'mbstring' => array('mbstring.func_overload=0'), 303 | ); 304 | 305 | foreach($info_params_ex as $ext => $ini_overwrites_ex) { 306 | if (in_array($ext, $exts_to_test)) { 307 | $ini_overwrites = array_merge($ini_overwrites, $ini_overwrites_ex); 308 | } 309 | } 310 | 311 | if (function_exists('opcache_invalidate')) { 312 | opcache_invalidate($info_file, true); 313 | } 314 | @unlink($info_file); 315 | 316 | // Write test context information. 317 | echo " 318 | ===================================================================== 319 | PHP : $php $php_info $php_cgi_info $phpdbg_info 320 | CWD : $cwd 321 | ===================================================================== 322 | "; 323 | } 324 | 325 | define('PHP_QA_EMAIL', 'qa-reports@lists.php.net'); 326 | define('QA_SUBMISSION_PAGE', 'http://qa.php.net/buildtest-process.php'); 327 | define('QA_REPORTS_PAGE', 'http://qa.php.net/reports'); 328 | define('TRAVIS_CI' , (bool) getenv('TRAVIS')); 329 | 330 | // Determine the tests to be run. 331 | 332 | $test_files = array(); 333 | $redir_tests = array(); 334 | $test_results = array(); 335 | $PHP_FAILED_TESTS = array('BORKED' => array(), 'FAILED' => array(), 'WARNED' => array(), 'LEAKED' => array(), 'XFAILED' => array(), 'SLOW' => array()); 336 | 337 | // If parameters given assume they represent selected tests to run. 338 | $result_tests_file= false; 339 | $failed_tests_file= false; 340 | $pass_option_n = false; 341 | $pass_options = ''; 342 | 343 | $output_file = $CUR_DIR . '/php_test_results_' . date('Ymd_Hi') . '.txt'; 344 | 345 | $just_save_results = false; 346 | $leak_check = false; 347 | $html_output = false; 348 | $html_file = null; 349 | $temp_source = null; 350 | $temp_target = null; 351 | $temp_urlbase = null; 352 | $conf_passed = null; 353 | $no_clean = false; 354 | $slow_min_ms = INF; 355 | 356 | $cfgtypes = array('show', 'keep'); 357 | $cfgfiles = array('skip', 'php', 'clean', 'out', 'diff', 'exp'); 358 | $cfg = array(); 359 | 360 | foreach($cfgtypes as $type) { 361 | $cfg[$type] = array(); 362 | 363 | foreach($cfgfiles as $file) { 364 | $cfg[$type][$file] = false; 365 | } 366 | } 367 | 368 | if (getenv('TEST_PHP_ARGS')) { 369 | 370 | if (!isset($argc, $argv) || !$argc) { 371 | $argv = array(__FILE__); 372 | } 373 | 374 | $argv = array_merge($argv, explode(' ', getenv('TEST_PHP_ARGS'))); 375 | $argc = count($argv); 376 | } 377 | 378 | if (isset($argc) && $argc > 1) { 379 | 380 | for ($i=1; $i<$argc; $i++) { 381 | $is_switch = false; 382 | $switch = substr($argv[$i],1,1); 383 | $repeat = substr($argv[$i],0,1) == '-'; 384 | 385 | while ($repeat) { 386 | 387 | if (!$is_switch) { 388 | $switch = substr($argv[$i],1,1); 389 | } 390 | 391 | $is_switch = true; 392 | 393 | if ($repeat) { 394 | foreach($cfgtypes as $type) { 395 | if (strpos($switch, '--' . $type) === 0) { 396 | foreach($cfgfiles as $file) { 397 | if ($switch == '--' . $type . '-' . $file) { 398 | $cfg[$type][$file] = true; 399 | $is_switch = false; 400 | break; 401 | } 402 | } 403 | } 404 | } 405 | } 406 | 407 | if (!$is_switch) { 408 | $is_switch = true; 409 | break; 410 | } 411 | 412 | $repeat = false; 413 | 414 | switch($switch) { 415 | case 'r': 416 | case 'l': 417 | $test_list = file($argv[++$i]); 418 | if ($test_list) { 419 | foreach($test_list as $test) { 420 | $matches = array(); 421 | if (preg_match('/^#.*\[(.*)\]\:\s+(.*)$/', $test, $matches)) { 422 | $redir_tests[] = array($matches[1], $matches[2]); 423 | } else if (strlen($test)) { 424 | $test_files[] = trim($test); 425 | } 426 | } 427 | } 428 | if ($switch != 'l') { 429 | break; 430 | } 431 | $i--; 432 | // break left intentionally 433 | case 'w': 434 | $failed_tests_file = fopen($argv[++$i], 'w+t'); 435 | break; 436 | case 'a': 437 | $failed_tests_file = fopen($argv[++$i], 'a+t'); 438 | break; 439 | case 'W': 440 | $result_tests_file = fopen($argv[++$i], 'w+t'); 441 | break; 442 | case 'c': 443 | $conf_passed = $argv[++$i]; 444 | break; 445 | case 'd': 446 | $ini_overwrites[] = $argv[++$i]; 447 | break; 448 | case 'g': 449 | $SHOW_ONLY_GROUPS = explode(",", $argv[++$i]); 450 | break; 451 | //case 'h' 452 | case '--keep-all': 453 | foreach($cfgfiles as $file) { 454 | $cfg['keep'][$file] = true; 455 | } 456 | break; 457 | //case 'l' 458 | case 'm': 459 | $leak_check = true; 460 | $valgrind_cmd = "valgrind --version"; 461 | $valgrind_header = system_with_timeout($valgrind_cmd, $environment); 462 | $replace_count = 0; 463 | if (!$valgrind_header) { 464 | error("Valgrind returned no version info, cannot proceed.\nPlease check if Valgrind is installed."); 465 | } else { 466 | $valgrind_version = preg_replace("/valgrind-(\d+)\.(\d+)\.(\d+)([.\w_-]+)?(\s+)/", '$1.$2.$3', $valgrind_header, 1, $replace_count); 467 | if ($replace_count != 1) { 468 | error("Valgrind returned invalid version info (\"$valgrind_header\"), cannot proceed."); 469 | } 470 | $valgrind_header = trim($valgrind_header); 471 | } 472 | break; 473 | case 'n': 474 | if (!$pass_option_n) { 475 | $pass_options .= ' -n'; 476 | } 477 | $pass_option_n = true; 478 | break; 479 | case 'e': 480 | $pass_options .= ' -e'; 481 | break; 482 | case '--no-clean': 483 | $no_clean = true; 484 | break; 485 | case 'p': 486 | $php = $argv[++$i]; 487 | putenv("TEST_PHP_EXECUTABLE=$php"); 488 | $environment['TEST_PHP_EXECUTABLE'] = $php; 489 | break; 490 | case 'P': 491 | if(constant('PHP_BINARY')) { 492 | $php = PHP_BINARY; 493 | } else { 494 | break; 495 | } 496 | putenv("TEST_PHP_EXECUTABLE=$php"); 497 | $environment['TEST_PHP_EXECUTABLE'] = $php; 498 | break; 499 | case 'q': 500 | putenv('NO_INTERACTION=1'); 501 | break; 502 | //case 'r' 503 | case 's': 504 | $output_file = $argv[++$i]; 505 | $just_save_results = true; 506 | break; 507 | case '--set-timeout': 508 | $environment['TEST_TIMEOUT'] = $argv[++$i]; 509 | break; 510 | case '--show-all': 511 | foreach($cfgfiles as $file) { 512 | $cfg['show'][$file] = true; 513 | } 514 | break; 515 | case '--show-slow': 516 | $slow_min_ms = $argv[++$i]; 517 | break; 518 | case '--temp-source': 519 | $temp_source = $argv[++$i]; 520 | break; 521 | case '--temp-target': 522 | $temp_target = $argv[++$i]; 523 | if ($temp_urlbase) { 524 | $temp_urlbase = $temp_target; 525 | } 526 | break; 527 | case '--temp-urlbase': 528 | $temp_urlbase = $argv[++$i]; 529 | break; 530 | case 'v': 531 | case '--verbose': 532 | $DETAILED = true; 533 | break; 534 | case 'x': 535 | $environment['SKIP_SLOW_TESTS'] = 1; 536 | break; 537 | case '--offline': 538 | $environment['SKIP_ONLINE_TESTS'] = 1; 539 | break; 540 | //case 'w' 541 | case '-': 542 | // repeat check with full switch 543 | $switch = $argv[$i]; 544 | if ($switch != '-') { 545 | $repeat = true; 546 | } 547 | break; 548 | case '--html': 549 | $html_file = fopen($argv[++$i], 'wt'); 550 | $html_output = is_resource($html_file); 551 | break; 552 | case '--version': 553 | echo '$Id: f4720f5b76a811a5f7ee1b19ce15e4c68ecf7307 $' . "\n"; 554 | exit(1); 555 | 556 | default: 557 | echo "Illegal switch '$switch' specified!\n"; 558 | case 'h': 559 | case '-help': 560 | case '--help': 561 | echo << Read the testfiles to be executed from . After the test 567 | has finished all failed tests are written to the same . 568 | If the list is empty and no further test is specified then 569 | all tests are executed (same as: -r -w ). 570 | 571 | -r Read the testfiles to be executed from . 572 | 573 | -w Write a list of all failed tests to . 574 | 575 | -a Same as -w but append rather then truncating . 576 | 577 | -W Write a list of all tests and their result status to . 578 | 579 | -c Look for php.ini in directory or use as ini. 580 | 581 | -n Pass -n option to the php binary (Do not use a php.ini). 582 | 583 | -d foo=bar Pass -d option to the php binary (Define INI entry foo 584 | with value 'bar'). 585 | 586 | -g Comma separated list of groups to show during test run 587 | (possible values: PASS, FAIL, XFAIL, SKIP, BORK, WARN, LEAK, REDIRECT). 588 | 589 | -m Test for memory leaks with Valgrind. 590 | 591 | -p Specify PHP executable to run. 592 | 593 | -P Use PHP_BINARY as PHP executable to run. 594 | 595 | -q Quiet, no user interaction (same as environment NO_INTERACTION). 596 | 597 | -s Write output to . 598 | 599 | -x Sets 'SKIP_SLOW_TESTS' environmental variable. 600 | 601 | --offline Sets 'SKIP_ONLINE_TESTS' environmental variable. 602 | 603 | --verbose 604 | -v Verbose mode. 605 | 606 | --help 607 | -h This Help. 608 | 609 | --html Generate HTML output. 610 | 611 | --temp-source --temp-target [--temp-urlbase ] 612 | Write temporary files to by replacing from the 613 | filenames to generate with . If --html is being used and 614 | given then the generated links are relative and prefixed 615 | with the given url. In general you want to make the path 616 | to your source files and some pach in your web page 617 | hierarchy with pointing to . 618 | 619 | --keep-[all|php|skip|clean] 620 | Do not delete 'all' files, 'php' test file, 'skip' or 'clean' 621 | file. 622 | 623 | --set-timeout [n] 624 | Set timeout for individual tests, where [n] is the number of 625 | seconds. The default value is 60 seconds, or 300 seconds when 626 | testing for memory leaks. 627 | 628 | --show-[all|php|skip|clean|exp|diff|out] 629 | Show 'all' files, 'php' test file, 'skip' or 'clean' file. You 630 | can also use this to show the output 'out', the expected result 631 | 'exp' or the difference between them 'diff'. The result types 632 | get written independent of the log format, however 'diff' only 633 | exists when a test fails. 634 | 635 | --show-slow [n] 636 | Show all tests that took longer than [n] milliseconds to run. 637 | 638 | --no-clean Do not execute clean section if any. 639 | 640 | HELP; 641 | exit(1); 642 | } 643 | } 644 | 645 | if (!$is_switch) { 646 | $testfile = realpath($argv[$i]); 647 | 648 | if (!$testfile && strpos($argv[$i], '*') !== false && function_exists('glob')) { 649 | 650 | if (preg_match("/\.phpt$/", $argv[$i])) { 651 | $pattern_match = glob($argv[$i]); 652 | } else if (preg_match("/\*$/", $argv[$i])) { 653 | $pattern_match = glob($argv[$i] . '.phpt'); 654 | } else { 655 | die("bogus test name " . $argv[$i] . "\n"); 656 | } 657 | 658 | if (is_array($pattern_match)) { 659 | $test_files = array_merge($test_files, $pattern_match); 660 | } 661 | 662 | } else if (is_dir($testfile)) { 663 | find_files($testfile); 664 | } else if (preg_match("/\.phpt$/", $testfile)) { 665 | $test_files[] = $testfile; 666 | } else { 667 | die("bogus test name " . $argv[$i] . "\n"); 668 | } 669 | } 670 | } 671 | 672 | if (strlen($conf_passed)) { 673 | if (substr(PHP_OS, 0, 3) == "WIN") { 674 | $pass_options .= " -c " . escapeshellarg($conf_passed); 675 | } else { 676 | $pass_options .= " -c '$conf_passed'"; 677 | } 678 | } 679 | 680 | $test_files = array_unique($test_files); 681 | $test_files = array_merge($test_files, $redir_tests); 682 | 683 | // Run selected tests. 684 | $test_cnt = count($test_files); 685 | 686 | if ($test_cnt) { 687 | putenv('NO_INTERACTION=1'); 688 | verify_config(); 689 | write_information(); 690 | usort($test_files, "test_sort"); 691 | $start_time = time(); 692 | 693 | if (!$html_output) { 694 | echo "Running selected tests.\n"; 695 | } else { 696 | show_start($start_time); 697 | } 698 | 699 | $test_idx = 0; 700 | run_all_tests($test_files, $environment); 701 | $end_time = time(); 702 | 703 | if ($html_output) { 704 | show_end($end_time); 705 | } 706 | 707 | if ($failed_tests_file) { 708 | fclose($failed_tests_file); 709 | } 710 | 711 | if ($result_tests_file) { 712 | fclose($result_tests_file); 713 | } 714 | 715 | compute_summary(); 716 | if ($html_output) { 717 | fwrite($html_file, "
\n" . get_summary(false, true)); 718 | } 719 | echo "====================================================================="; 720 | echo get_summary(false, false); 721 | 722 | if ($html_output) { 723 | fclose($html_file); 724 | } 725 | 726 | junit_save_xml(); 727 | 728 | if (getenv('REPORT_EXIT_STATUS') !== '0' && 729 | getenv('REPORT_EXIT_STATUS') !== 'no' && 730 | ($sum_results['FAILED'] || $sum_results['BORKED'])) { 731 | exit(1); 732 | } 733 | 734 | exit(0); 735 | } 736 | } 737 | 738 | verify_config(); 739 | write_information(); 740 | 741 | // Compile a list of all test files (*.phpt). 742 | $test_files = array(); 743 | $exts_tested = count($exts_to_test); 744 | $exts_skipped = 0; 745 | $ignored_by_ext = 0; 746 | sort($exts_to_test); 747 | $test_dirs = array(); 748 | $optionals = array('tests', 'ext', 'Zend', 'sapi'); 749 | 750 | foreach($optionals as $dir) { 751 | if (@filetype($dir) == 'dir') { 752 | $test_dirs[] = $dir; 753 | } 754 | } 755 | 756 | // Convert extension names to lowercase 757 | foreach ($exts_to_test as $key => $val) { 758 | $exts_to_test[$key] = strtolower($val); 759 | } 760 | 761 | foreach ($test_dirs as $dir) { 762 | find_files("{$cwd}/{$dir}", ($dir == 'ext')); 763 | } 764 | 765 | foreach ($user_tests as $dir) { 766 | find_files($dir, ($dir == 'ext')); 767 | } 768 | 769 | function find_files($dir, $is_ext_dir = false, $ignore = false) 770 | { 771 | global $test_files, $exts_to_test, $ignored_by_ext, $exts_skipped; 772 | 773 | $o = opendir($dir) or error("cannot open directory: $dir"); 774 | 775 | while (($name = readdir($o)) !== false) { 776 | 777 | if (is_dir("{$dir}/{$name}") && !in_array($name, array('.', '..', '.svn'))) { 778 | $skip_ext = ($is_ext_dir && !in_array(strtolower($name), $exts_to_test)); 779 | if ($skip_ext) { 780 | $exts_skipped++; 781 | } 782 | find_files("{$dir}/{$name}", false, $ignore || $skip_ext); 783 | } 784 | 785 | // Cleanup any left-over tmp files from last run. 786 | if (substr($name, -4) == '.tmp') { 787 | @unlink("$dir/$name"); 788 | continue; 789 | } 790 | 791 | // Otherwise we're only interested in *.phpt files. 792 | if (substr($name, -5) == '.phpt') { 793 | if ($ignore) { 794 | $ignored_by_ext++; 795 | } else { 796 | $testfile = realpath("{$dir}/{$name}"); 797 | $test_files[] = $testfile; 798 | } 799 | } 800 | } 801 | 802 | closedir($o); 803 | } 804 | 805 | function test_name($name) 806 | { 807 | if (is_array($name)) { 808 | return $name[0] . ':' . $name[1]; 809 | } else { 810 | return $name; 811 | } 812 | } 813 | 814 | function test_sort($a, $b) 815 | { 816 | global $cwd; 817 | 818 | $a = test_name($a); 819 | $b = test_name($b); 820 | 821 | $ta = strpos($a, "{$cwd}/tests") === 0 ? 1 + (strpos($a, "{$cwd}/tests/run-test") === 0 ? 1 : 0) : 0; 822 | $tb = strpos($b, "{$cwd}/tests") === 0 ? 1 + (strpos($b, "{$cwd}/tests/run-test") === 0 ? 1 : 0) : 0; 823 | 824 | if ($ta == $tb) { 825 | return strcmp($a, $b); 826 | } else { 827 | return $tb - $ta; 828 | } 829 | } 830 | 831 | $test_files = array_unique($test_files); 832 | usort($test_files, "test_sort"); 833 | 834 | $start_time = time(); 835 | show_start($start_time); 836 | 837 | $test_cnt = count($test_files); 838 | $test_idx = 0; 839 | run_all_tests($test_files, $environment); 840 | $end_time = time(); 841 | 842 | if ($failed_tests_file) { 843 | fclose($failed_tests_file); 844 | } 845 | 846 | if ($result_tests_file) { 847 | fclose($result_tests_file); 848 | } 849 | 850 | // Summarize results 851 | 852 | if (0 == count($test_results)) { 853 | echo "No tests were run.\n"; 854 | return; 855 | } 856 | 857 | compute_summary(); 858 | 859 | show_end($end_time); 860 | show_summary(); 861 | 862 | if ($html_output) { 863 | fclose($html_file); 864 | } 865 | 866 | junit_save_xml(); 867 | 868 | if (getenv('REPORT_EXIT_STATUS') !== '0' && 869 | getenv('REPORT_EXIT_STATUS') !== 'no' && 870 | ($sum_results['FAILED'] || $sum_results['BORKED'])) { 871 | exit(1); 872 | } 873 | exit(0); 874 | 875 | // 876 | // Send Email to QA Team 877 | // 878 | 879 | function mail_qa_team($data, $status = false) 880 | { 881 | $url_bits = parse_url(QA_SUBMISSION_PAGE); 882 | 883 | if (($proxy = getenv('http_proxy'))) { 884 | $proxy = parse_url($proxy); 885 | $path = $url_bits['host'].$url_bits['path']; 886 | $host = $proxy['host']; 887 | if (empty($proxy['port'])) { 888 | $proxy['port'] = 80; 889 | } 890 | $port = $proxy['port']; 891 | } else { 892 | $path = $url_bits['path']; 893 | $host = $url_bits['host']; 894 | $port = empty($url_bits['port']) ? 80 : $port = $url_bits['port']; 895 | } 896 | 897 | $data = "php_test_data=" . urlencode(base64_encode(str_replace("\00", '[0x0]', $data))); 898 | $data_length = strlen($data); 899 | 900 | $fs = fsockopen($host, $port, $errno, $errstr, 10); 901 | 902 | if (!$fs) { 903 | return false; 904 | } 905 | 906 | $php_version = urlencode(TESTED_PHP_VERSION); 907 | 908 | echo "\nPosting to ". QA_SUBMISSION_PAGE . "\n"; 909 | fwrite($fs, "POST " . $path . "?status=$status&version=$php_version HTTP/1.1\r\n"); 910 | fwrite($fs, "Host: " . $host . "\r\n"); 911 | fwrite($fs, "User-Agent: QA Browser 0.1\r\n"); 912 | fwrite($fs, "Content-Type: application/x-www-form-urlencoded\r\n"); 913 | fwrite($fs, "Content-Length: " . $data_length . "\r\n\r\n"); 914 | fwrite($fs, $data); 915 | fwrite($fs, "\r\n\r\n"); 916 | fclose($fs); 917 | 918 | return 1; 919 | } 920 | 921 | 922 | // 923 | // Write the given text to a temporary file, and return the filename. 924 | // 925 | 926 | function save_text($filename, $text, $filename_copy = null) 927 | { 928 | global $DETAILED; 929 | 930 | if ($filename_copy && $filename_copy != $filename) { 931 | if (file_put_contents($filename_copy, $text, FILE_BINARY) === false) { 932 | error("Cannot open file '" . $filename_copy . "' (save_text)"); 933 | } 934 | } 935 | 936 | if (file_put_contents($filename, $text, FILE_BINARY) === false) { 937 | error("Cannot open file '" . $filename . "' (save_text)"); 938 | } 939 | 940 | if (1 < $DETAILED) echo " 941 | FILE $filename {{{ 942 | $text 943 | }}} 944 | "; 945 | } 946 | 947 | // 948 | // Write an error in a format recognizable to Emacs or MSVC. 949 | // 950 | 951 | function error_report($testname, $logname, $tested) 952 | { 953 | $testname = realpath($testname); 954 | $logname = realpath($logname); 955 | 956 | switch (strtoupper(getenv('TEST_PHP_ERROR_STYLE'))) { 957 | case 'MSVC': 958 | echo $testname . "(1) : $tested\n"; 959 | echo $logname . "(1) : $tested\n"; 960 | break; 961 | case 'EMACS': 962 | echo $testname . ":1: $tested\n"; 963 | echo $logname . ":1: $tested\n"; 964 | break; 965 | } 966 | } 967 | 968 | function system_with_timeout($commandline, $env = null, $stdin = null, $captureStdIn = true, $captureStdOut = true, $captureStdErr = true) 969 | { 970 | global $leak_check, $cwd; 971 | 972 | $data = ''; 973 | 974 | $bin_env = array(); 975 | foreach((array)$env as $key => $value) { 976 | $bin_env[$key] = $value; 977 | } 978 | 979 | $descriptorspec = array(); 980 | if ($captureStdIn) { 981 | $descriptorspec[0] = array('pipe', 'r'); 982 | } 983 | if ($captureStdOut) { 984 | $descriptorspec[1] = array('pipe', 'w'); 985 | } 986 | if ($captureStdErr) { 987 | $descriptorspec[2] = array('pipe', 'w'); 988 | } 989 | $proc = proc_open($commandline, $descriptorspec, $pipes, $cwd, $bin_env, array('suppress_errors' => true, 'binary_pipes' => true)); 990 | 991 | if (!$proc) { 992 | return false; 993 | } 994 | 995 | if ($captureStdIn) { 996 | if (!is_null($stdin)) { 997 | fwrite($pipes[0], $stdin); 998 | } 999 | fclose($pipes[0]); 1000 | unset($pipes[0]); 1001 | } 1002 | 1003 | $timeout = $leak_check ? 300 : (isset($env['TEST_TIMEOUT']) ? $env['TEST_TIMEOUT'] : 60); 1004 | 1005 | while (true) { 1006 | /* hide errors from interrupted syscalls */ 1007 | $r = $pipes; 1008 | $w = null; 1009 | $e = null; 1010 | 1011 | $n = @stream_select($r, $w, $e, $timeout); 1012 | 1013 | if ($n === false) { 1014 | break; 1015 | } else if ($n === 0) { 1016 | /* timed out */ 1017 | $data .= "\n ** ERROR: process timed out **\n"; 1018 | proc_terminate($proc, 9); 1019 | return $data; 1020 | } else if ($n > 0) { 1021 | if ($captureStdOut) { 1022 | $line = fread($pipes[1], 8192); 1023 | } elseif ($captureStdErr) { 1024 | $line = fread($pipes[2], 8192); 1025 | } else { 1026 | $line = ''; 1027 | } 1028 | if (strlen($line) == 0) { 1029 | /* EOF */ 1030 | break; 1031 | } 1032 | $data .= $line; 1033 | } 1034 | } 1035 | 1036 | $stat = proc_get_status($proc); 1037 | 1038 | if ($stat['signaled']) { 1039 | $data .= "\nTermsig=" . $stat['stopsig'] . "\n"; 1040 | } 1041 | if ($stat["exitcode"] > 128 && $stat["exitcode"] < 160) { 1042 | $data .= "\nTermsig=" . ($stat["exitcode"] - 128) . "\n"; 1043 | } 1044 | 1045 | proc_close($proc); 1046 | return $data; 1047 | } 1048 | 1049 | function run_all_tests($test_files, $env, $redir_tested = null) 1050 | { 1051 | global $test_results, $failed_tests_file, $result_tests_file, $php, $test_idx; 1052 | 1053 | foreach($test_files as $name) { 1054 | 1055 | if (is_array($name)) { 1056 | $index = "# $name[1]: $name[0]"; 1057 | 1058 | if ($redir_tested) { 1059 | $name = $name[0]; 1060 | } 1061 | } else if ($redir_tested) { 1062 | $index = "# $redir_tested: $name"; 1063 | } else { 1064 | $index = $name; 1065 | } 1066 | $test_idx++; 1067 | $result = run_test($php, $name, $env); 1068 | 1069 | if (!is_array($name) && $result != 'REDIR') { 1070 | $test_results[$index] = $result; 1071 | if ($failed_tests_file && ($result == 'XFAILED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) { 1072 | fwrite($failed_tests_file, "$index\n"); 1073 | } 1074 | if ($result_tests_file) { 1075 | fwrite($result_tests_file, "$result\t$index\n"); 1076 | } 1077 | } 1078 | } 1079 | } 1080 | 1081 | // 1082 | // Show file or result block 1083 | // 1084 | function show_file_block($file, $block, $section = null) 1085 | { 1086 | global $cfg; 1087 | 1088 | if ($cfg['show'][$file]) { 1089 | 1090 | if (is_null($section)) { 1091 | $section = strtoupper($file); 1092 | } 1093 | 1094 | echo "\n========" . $section . "========\n"; 1095 | echo rtrim($block); 1096 | echo "\n========DONE========\n"; 1097 | } 1098 | } 1099 | 1100 | // 1101 | // Run an individual test case. 1102 | // 1103 | function run_test($php, $file, $env) 1104 | { 1105 | global $log_format, $ini_overwrites, $cwd, $PHP_FAILED_TESTS; 1106 | global $pass_options, $DETAILED, $IN_REDIRECT, $test_cnt, $test_idx; 1107 | global $leak_check, $temp_source, $temp_target, $cfg, $environment; 1108 | global $no_clean; 1109 | global $valgrind_version; 1110 | global $SHOW_ONLY_GROUPS; 1111 | global $no_file_cache; 1112 | global $slow_min_ms; 1113 | $temp_filenames = null; 1114 | $org_file = $file; 1115 | 1116 | if (isset($env['TEST_PHP_CGI_EXECUTABLE'])) { 1117 | $php_cgi = $env['TEST_PHP_CGI_EXECUTABLE']; 1118 | } 1119 | 1120 | if (isset($env['TEST_PHPDBG_EXECUTABLE'])) { 1121 | $phpdbg = $env['TEST_PHPDBG_EXECUTABLE']; 1122 | } 1123 | 1124 | if (is_array($file)) { 1125 | $file = $file[0]; 1126 | } 1127 | 1128 | if ($DETAILED) echo " 1129 | ================= 1130 | TEST $file 1131 | "; 1132 | 1133 | // Load the sections of the test file. 1134 | $section_text = array('TEST' => ''); 1135 | 1136 | $fp = fopen($file, "rb") or error("Cannot open test file: $file"); 1137 | 1138 | $borked = false; 1139 | $bork_info = ''; 1140 | 1141 | if (!feof($fp)) { 1142 | $line = fgets($fp); 1143 | 1144 | if ($line === false) { 1145 | $bork_info = "cannot read test"; 1146 | $borked = true; 1147 | } 1148 | } else { 1149 | $bork_info = "empty test [$file]"; 1150 | $borked = true; 1151 | } 1152 | if (!$borked && strncmp('--TEST--', $line, 8)) { 1153 | $bork_info = "tests must start with --TEST-- [$file]"; 1154 | $borked = true; 1155 | } 1156 | 1157 | $section = 'TEST'; 1158 | $secfile = false; 1159 | $secdone = false; 1160 | 1161 | while (!feof($fp)) { 1162 | $line = fgets($fp); 1163 | 1164 | if ($line === false) { 1165 | break; 1166 | } 1167 | 1168 | // Match the beginning of a section. 1169 | if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { 1170 | $section = $r[1]; 1171 | settype($section, 'string'); 1172 | 1173 | if (isset($section_text[$section])) { 1174 | $bork_info = "duplicated $section section"; 1175 | $borked = true; 1176 | } 1177 | 1178 | $section_text[$section] = ''; 1179 | $secfile = $section == 'FILE' || $section == 'FILEEOF' || $section == 'FILE_EXTERNAL'; 1180 | $secdone = false; 1181 | continue; 1182 | } 1183 | 1184 | // Add to the section text. 1185 | if (!$secdone) { 1186 | $section_text[$section] .= $line; 1187 | } 1188 | 1189 | // End of actual test? 1190 | if ($secfile && preg_match('/^===DONE===\s*$/', $line)) { 1191 | $secdone = true; 1192 | } 1193 | } 1194 | 1195 | // the redirect section allows a set of tests to be reused outside of 1196 | // a given test dir 1197 | if (!$borked) { 1198 | if (@count($section_text['REDIRECTTEST']) == 1) { 1199 | 1200 | if ($IN_REDIRECT) { 1201 | $borked = true; 1202 | $bork_info = "Can't redirect a test from within a redirected test"; 1203 | } else { 1204 | $borked = false; 1205 | } 1206 | 1207 | } else { 1208 | 1209 | if (!isset($section_text['PHPDBG']) && @count($section_text['FILE']) + @count($section_text['FILEEOF']) + @count($section_text['FILE_EXTERNAL']) != 1) { 1210 | $bork_info = "missing section --FILE--"; 1211 | $borked = true; 1212 | } 1213 | 1214 | if (@count($section_text['FILEEOF']) == 1) { 1215 | $section_text['FILE'] = preg_replace("/[\r\n]+$/", '', $section_text['FILEEOF']); 1216 | unset($section_text['FILEEOF']); 1217 | } 1218 | 1219 | foreach (array( 'FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX' ) as $prefix) { 1220 | $key = $prefix . '_EXTERNAL'; 1221 | 1222 | if (@count($section_text[$key]) == 1) { 1223 | // don't allow tests to retrieve files from anywhere but this subdirectory 1224 | $section_text[$key] = dirname($file) . '/' . trim(str_replace('..', '', $section_text[$key])); 1225 | 1226 | if (file_exists($section_text[$key])) { 1227 | $section_text[$prefix] = file_get_contents($section_text[$key], FILE_BINARY); 1228 | unset($section_text[$key]); 1229 | } else { 1230 | $bork_info = "could not load --" . $key . "-- " . dirname($file) . '/' . trim($section_text[$key]); 1231 | $borked = true; 1232 | } 1233 | } 1234 | } 1235 | 1236 | if ((@count($section_text['EXPECT']) + @count($section_text['EXPECTF']) + @count($section_text['EXPECTREGEX'])) != 1) { 1237 | $bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--"; 1238 | $borked = true; 1239 | } 1240 | } 1241 | } 1242 | fclose($fp); 1243 | 1244 | $shortname = str_replace($cwd . '/', '', $file); 1245 | $tested_file = $shortname; 1246 | 1247 | if ($borked) { 1248 | show_result("BORK", $bork_info, $tested_file); 1249 | $PHP_FAILED_TESTS['BORKED'][] = array ( 1250 | 'name' => $file, 1251 | 'test_name' => '', 1252 | 'output' => '', 1253 | 'diff' => '', 1254 | 'info' => "$bork_info [$file]", 1255 | ); 1256 | 1257 | junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info); 1258 | return 'BORKED'; 1259 | } 1260 | 1261 | if (isset($section_text['CAPTURE_STDIO'])) { 1262 | $captureStdIn = stripos($section_text['CAPTURE_STDIO'], 'STDIN') !== false; 1263 | $captureStdOut = stripos($section_text['CAPTURE_STDIO'], 'STDOUT') !== false; 1264 | $captureStdErr = stripos($section_text['CAPTURE_STDIO'], 'STDERR') !== false; 1265 | } else { 1266 | $captureStdIn = true; 1267 | $captureStdOut = true; 1268 | $captureStdErr = true; 1269 | } 1270 | if ($captureStdOut && $captureStdErr) { 1271 | $cmdRedirect = ' 2>&1'; 1272 | } else { 1273 | $cmdRedirect = ''; 1274 | } 1275 | 1276 | $tested = trim($section_text['TEST']); 1277 | 1278 | /* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */ 1279 | if (!empty($section_text['GET']) || !empty($section_text['POST']) || !empty($section_text['GZIP_POST']) || !empty($section_text['DEFLATE_POST']) || !empty($section_text['POST_RAW']) || !empty($section_text['PUT']) || !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { 1280 | if (isset($php_cgi)) { 1281 | $old_php = $php; 1282 | $php = $php_cgi . ' -C '; 1283 | } else if (!strncasecmp(PHP_OS, "win", 3) && file_exists(dirname($php) . "/php-cgi.exe")) { 1284 | $old_php = $php; 1285 | $php = realpath(dirname($php) . "/php-cgi.exe") . ' -C '; 1286 | } else { 1287 | if (file_exists(dirname($php) . "/../../sapi/cgi/php-cgi")) { 1288 | $old_php = $php; 1289 | $php = realpath(dirname($php) . "/../../sapi/cgi/php-cgi") . ' -C '; 1290 | } else if (file_exists("./sapi/cgi/php-cgi")) { 1291 | $old_php = $php; 1292 | $php = realpath("./sapi/cgi/php-cgi") . ' -C '; 1293 | } else if (file_exists(dirname($php) . "/php-cgi")) { 1294 | $old_php = $php; 1295 | $php = realpath(dirname($php) . "/php-cgi") . ' -C '; 1296 | } else { 1297 | show_result('SKIP', $tested, $tested_file, "reason: CGI not available"); 1298 | 1299 | junit_init_suite(junit_get_suitename_for($shortname)); 1300 | junit_mark_test_as('SKIP', $shortname, $tested, 0, 'CGI not available'); 1301 | return 'SKIPPED'; 1302 | } 1303 | } 1304 | $uses_cgi = true; 1305 | } 1306 | 1307 | /* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */ 1308 | if (array_key_exists('PHPDBG', $section_text)) { 1309 | if (!isset($section_text['STDIN'])) { 1310 | $section_text['STDIN'] = $section_text['PHPDBG']."\n"; 1311 | } 1312 | 1313 | if (isset($phpdbg)) { 1314 | $old_php = $php; 1315 | $php = $phpdbg . ' -qIb'; 1316 | } else { 1317 | show_result('SKIP', $tested, $tested_file, "reason: phpdbg not available"); 1318 | 1319 | junit_init_suite(junit_get_suitename_for($shortname)); 1320 | junit_mark_test_as('SKIP', $shortname, $tested, 0, 'phpdbg not available'); 1321 | return 'SKIPPED'; 1322 | } 1323 | } 1324 | 1325 | if (!$SHOW_ONLY_GROUPS) { 1326 | show_test($test_idx, $shortname); 1327 | } 1328 | 1329 | if (is_array($IN_REDIRECT)) { 1330 | $temp_dir = $test_dir = $IN_REDIRECT['dir']; 1331 | } else { 1332 | $temp_dir = $test_dir = realpath(dirname($file)); 1333 | } 1334 | 1335 | if ($temp_source && $temp_target) { 1336 | $temp_dir = str_replace($temp_source, $temp_target, $temp_dir); 1337 | } 1338 | 1339 | $main_file_name = basename($file,'phpt'); 1340 | 1341 | $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'diff'; 1342 | $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'log'; 1343 | $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'exp'; 1344 | $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'out'; 1345 | $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'mem'; 1346 | $sh_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'sh'; 1347 | $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; 1348 | $test_file = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; 1349 | $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; 1350 | $test_skipif = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; 1351 | $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; 1352 | $test_clean = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; 1353 | $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . uniqid('/phpt.'); 1354 | $tmp_relative_file = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $test_file) . 't'; 1355 | 1356 | if ($temp_source && $temp_target) { 1357 | $temp_skipif .= 's'; 1358 | $temp_file .= 's'; 1359 | $temp_clean .= 's'; 1360 | $copy_file = $temp_dir . DIRECTORY_SEPARATOR . basename(is_array($file) ? $file[1] : $file) . '.phps'; 1361 | 1362 | if (!is_dir(dirname($copy_file))) { 1363 | mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file)); 1364 | } 1365 | 1366 | if (isset($section_text['FILE'])) { 1367 | save_text($copy_file, $section_text['FILE']); 1368 | } 1369 | 1370 | $temp_filenames = array( 1371 | 'file' => $copy_file, 1372 | 'diff' => $diff_filename, 1373 | 'log' => $log_filename, 1374 | 'exp' => $exp_filename, 1375 | 'out' => $output_filename, 1376 | 'mem' => $memcheck_filename, 1377 | 'sh' => $sh_filename, 1378 | 'php' => $temp_file, 1379 | 'skip' => $temp_skipif, 1380 | 'clean'=> $temp_clean); 1381 | } 1382 | 1383 | if (is_array($IN_REDIRECT)) { 1384 | $tested = $IN_REDIRECT['prefix'] . ' ' . trim($section_text['TEST']); 1385 | $tested_file = $tmp_relative_file; 1386 | } 1387 | 1388 | // unlink old test results 1389 | @unlink($diff_filename); 1390 | @unlink($log_filename); 1391 | @unlink($exp_filename); 1392 | @unlink($output_filename); 1393 | @unlink($memcheck_filename); 1394 | @unlink($sh_filename); 1395 | @unlink($temp_file); 1396 | @unlink($test_file); 1397 | @unlink($temp_skipif); 1398 | @unlink($test_skipif); 1399 | @unlink($tmp_post); 1400 | @unlink($temp_clean); 1401 | @unlink($test_clean); 1402 | 1403 | // Reset environment from any previous test. 1404 | $env['REDIRECT_STATUS'] = ''; 1405 | $env['QUERY_STRING'] = ''; 1406 | $env['PATH_TRANSLATED'] = ''; 1407 | $env['SCRIPT_FILENAME'] = ''; 1408 | $env['REQUEST_METHOD'] = ''; 1409 | $env['CONTENT_TYPE'] = ''; 1410 | $env['CONTENT_LENGTH'] = ''; 1411 | $env['TZ'] = ''; 1412 | 1413 | if (!empty($section_text['ENV'])) { 1414 | 1415 | foreach(explode("\n", trim($section_text['ENV'])) as $e) { 1416 | $e = explode('=', trim($e), 2); 1417 | 1418 | if (!empty($e[0]) && isset($e[1])) { 1419 | $env[$e[0]] = $e[1]; 1420 | } 1421 | } 1422 | } 1423 | 1424 | // Default ini settings 1425 | $ini_settings = array(); 1426 | 1427 | // Additional required extensions 1428 | if (array_key_exists('EXTENSIONS', $section_text)) { 1429 | $ext_params = array(); 1430 | settings2array($ini_overwrites, $ext_params); 1431 | settings2params($ext_params); 1432 | $ext_dir=`$php $pass_options $ext_params -d display_errors=0 -r "echo ini_get('extension_dir');"`; 1433 | $extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS'])); 1434 | $loaded = explode(",", `$php $pass_options $ext_params -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`); 1435 | $ext_prefix = substr(PHP_OS, 0, 3) === "WIN" ? "php_" : ""; 1436 | foreach ($extensions as $req_ext) { 1437 | if (!in_array($req_ext, $loaded)) { 1438 | if ($req_ext == 'opcache') { 1439 | $ini_settings['zend_extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; 1440 | } else { 1441 | $ini_settings['extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; 1442 | } 1443 | } 1444 | } 1445 | } 1446 | 1447 | // additional ini overwrites 1448 | //$ini_overwrites[] = 'setting=value'; 1449 | settings2array($ini_overwrites, $ini_settings); 1450 | 1451 | // Any special ini settings 1452 | // these may overwrite the test defaults... 1453 | if (array_key_exists('INI', $section_text)) { 1454 | if (strpos($section_text['INI'], '{PWD}') !== false) { 1455 | $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); 1456 | } 1457 | settings2array(preg_split( "/[\n\r]+/", $section_text['INI']), $ini_settings); 1458 | } 1459 | 1460 | settings2params($ini_settings); 1461 | 1462 | // Check if test should be skipped. 1463 | $info = ''; 1464 | $warn = false; 1465 | 1466 | if (array_key_exists('SKIPIF', $section_text)) { 1467 | 1468 | if (trim($section_text['SKIPIF'])) { 1469 | show_file_block('skip', $section_text['SKIPIF']); 1470 | save_text($test_skipif, $section_text['SKIPIF'], $temp_skipif); 1471 | $extra = substr(PHP_OS, 0, 3) !== "WIN" ? 1472 | "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;": ""; 1473 | 1474 | if ($leak_check) { 1475 | $env['USE_ZEND_ALLOC'] = '0'; 1476 | $env['ZEND_DONT_UNLOAD_MODULES'] = 1; 1477 | } else { 1478 | $env['USE_ZEND_ALLOC'] = '1'; 1479 | $env['ZEND_DONT_UNLOAD_MODULES'] = 0; 1480 | } 1481 | 1482 | junit_start_timer($shortname); 1483 | 1484 | $output = system_with_timeout("$extra $php $pass_options -q $ini_settings $no_file_cache -d display_errors=0 \"$test_skipif\"", $env); 1485 | 1486 | junit_finish_timer($shortname); 1487 | 1488 | if (!$cfg['keep']['skip']) { 1489 | @unlink($test_skipif); 1490 | } 1491 | 1492 | if (!strncasecmp('skip', ltrim($output), 4)) { 1493 | 1494 | if (preg_match('/^\s*skip\s*(.+)\s*/i', $output, $m)) { 1495 | show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames); 1496 | } else { 1497 | show_result('SKIP', $tested, $tested_file, '', $temp_filenames); 1498 | } 1499 | 1500 | if (!$cfg['keep']['skip']) { 1501 | @unlink($test_skipif); 1502 | } 1503 | 1504 | $message = !empty($m[1]) ? $m[1] : ''; 1505 | junit_mark_test_as('SKIP', $shortname, $tested, null, $message); 1506 | return 'SKIPPED'; 1507 | } 1508 | 1509 | if (!strncasecmp('info', ltrim($output), 4)) { 1510 | if (preg_match('/^\s*info\s*(.+)\s*/i', $output, $m)) { 1511 | $info = " (info: $m[1])"; 1512 | } 1513 | } 1514 | 1515 | if (!strncasecmp('warn', ltrim($output), 4)) { 1516 | if (preg_match('/^\s*warn\s*(.+)\s*/i', $output, $m)) { 1517 | $warn = true; /* only if there is a reason */ 1518 | $info = " (warn: $m[1])"; 1519 | } 1520 | } 1521 | 1522 | if (!strncasecmp('xfail', ltrim($output), 5)) { 1523 | // Pretend we have an XFAIL section 1524 | $section_text['XFAIL'] = trim(substr(ltrim($output), 5)); 1525 | } 1526 | } 1527 | } 1528 | 1529 | if (!extension_loaded("zlib") 1530 | && ( array_key_exists("GZIP_POST", $section_text) 1531 | || array_key_exists("DEFLATE_POST", $section_text)) 1532 | ) { 1533 | $message = "ext/zlib required"; 1534 | show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames); 1535 | junit_mark_test_as('SKIP', $shortname, $tested, null, $message); 1536 | return 'SKIPPED'; 1537 | } 1538 | 1539 | if (@count($section_text['REDIRECTTEST']) == 1) { 1540 | $test_files = array(); 1541 | 1542 | $IN_REDIRECT = eval($section_text['REDIRECTTEST']); 1543 | $IN_REDIRECT['via'] = "via [$shortname]\n\t"; 1544 | $IN_REDIRECT['dir'] = realpath(dirname($file)); 1545 | $IN_REDIRECT['prefix'] = trim($section_text['TEST']); 1546 | 1547 | if (!empty($IN_REDIRECT['TESTS'])) { 1548 | 1549 | if (is_array($org_file)) { 1550 | $test_files[] = $org_file[1]; 1551 | } else { 1552 | $GLOBALS['test_files'] = $test_files; 1553 | find_files($IN_REDIRECT['TESTS']); 1554 | 1555 | foreach($GLOBALS['test_files'] as $f) { 1556 | $test_files[] = array($f, $file); 1557 | } 1558 | } 1559 | $test_cnt += @count($test_files) - 1; 1560 | $test_idx--; 1561 | 1562 | show_redirect_start($IN_REDIRECT['TESTS'], $tested, $tested_file); 1563 | 1564 | // set up environment 1565 | $redirenv = array_merge($environment, $IN_REDIRECT['ENV']); 1566 | $redirenv['REDIR_TEST_DIR'] = realpath($IN_REDIRECT['TESTS']) . DIRECTORY_SEPARATOR; 1567 | 1568 | usort($test_files, "test_sort"); 1569 | run_all_tests($test_files, $redirenv, $tested); 1570 | 1571 | show_redirect_ends($IN_REDIRECT['TESTS'], $tested, $tested_file); 1572 | 1573 | // a redirected test never fails 1574 | $IN_REDIRECT = false; 1575 | 1576 | junit_mark_test_as('PASS', $shortname, $tested); 1577 | return 'REDIR'; 1578 | 1579 | } else { 1580 | 1581 | $bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory."; 1582 | show_result("BORK", $bork_info, '', $temp_filenames); 1583 | $PHP_FAILED_TESTS['BORKED'][] = array ( 1584 | 'name' => $file, 1585 | 'test_name' => '', 1586 | 'output' => '', 1587 | 'diff' => '', 1588 | 'info' => "$bork_info [$file]", 1589 | ); 1590 | } 1591 | } 1592 | 1593 | if (is_array($org_file) || @count($section_text['REDIRECTTEST']) == 1) { 1594 | 1595 | if (is_array($org_file)) { 1596 | $file = $org_file[0]; 1597 | } 1598 | 1599 | $bork_info = "Redirected test did not contain redirection info"; 1600 | show_result("BORK", $bork_info, '', $temp_filenames); 1601 | $PHP_FAILED_TESTS['BORKED'][] = array ( 1602 | 'name' => $file, 1603 | 'test_name' => '', 1604 | 'output' => '', 1605 | 'diff' => '', 1606 | 'info' => "$bork_info [$file]", 1607 | ); 1608 | 1609 | junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info); 1610 | 1611 | return 'BORKED'; 1612 | } 1613 | 1614 | // We've satisfied the preconditions - run the test! 1615 | if (isset($section_text['FILE'])) { 1616 | show_file_block('php', $section_text['FILE'], 'TEST'); 1617 | save_text($test_file, $section_text['FILE'], $temp_file); 1618 | } else { 1619 | $test_file = $temp_file = ""; 1620 | } 1621 | 1622 | if (array_key_exists('GET', $section_text)) { 1623 | $query_string = trim($section_text['GET']); 1624 | } else { 1625 | $query_string = ''; 1626 | } 1627 | 1628 | $env['REDIRECT_STATUS'] = '1'; 1629 | if (empty($env['QUERY_STRING'])) { 1630 | $env['QUERY_STRING'] = $query_string; 1631 | } 1632 | if (empty($env['PATH_TRANSLATED'])) { 1633 | $env['PATH_TRANSLATED'] = $test_file; 1634 | } 1635 | if (empty($env['SCRIPT_FILENAME'])) { 1636 | $env['SCRIPT_FILENAME'] = $test_file; 1637 | } 1638 | 1639 | if (array_key_exists('COOKIE', $section_text)) { 1640 | $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); 1641 | } else { 1642 | $env['HTTP_COOKIE'] = ''; 1643 | } 1644 | 1645 | $args = isset($section_text['ARGS']) ? ' -- ' . $section_text['ARGS'] : ''; 1646 | 1647 | if (array_key_exists('POST_RAW', $section_text) && !empty($section_text['POST_RAW'])) { 1648 | 1649 | $post = trim($section_text['POST_RAW']); 1650 | $raw_lines = explode("\n", $post); 1651 | 1652 | $request = ''; 1653 | $started = false; 1654 | 1655 | foreach ($raw_lines as $line) { 1656 | 1657 | if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) { 1658 | $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); 1659 | continue; 1660 | } 1661 | 1662 | if ($started) { 1663 | $request .= "\n"; 1664 | } 1665 | 1666 | $started = true; 1667 | $request .= $line; 1668 | } 1669 | 1670 | $env['CONTENT_LENGTH'] = strlen($request); 1671 | $env['REQUEST_METHOD'] = 'POST'; 1672 | 1673 | if (empty($request)) { 1674 | junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request'); 1675 | return 'BORKED'; 1676 | } 1677 | 1678 | save_text($tmp_post, $request); 1679 | $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 1680 | 1681 | } elseif (array_key_exists('PUT', $section_text) && !empty($section_text['PUT'])) { 1682 | 1683 | $post = trim($section_text['PUT']); 1684 | $raw_lines = explode("\n", $post); 1685 | 1686 | $request = ''; 1687 | $started = false; 1688 | 1689 | foreach ($raw_lines as $line) { 1690 | 1691 | if (empty($env['CONTENT_TYPE']) && preg_match('/^Content-Type:(.*)/i', $line, $res)) { 1692 | $env['CONTENT_TYPE'] = trim(str_replace("\r", '', $res[1])); 1693 | continue; 1694 | } 1695 | 1696 | if ($started) { 1697 | $request .= "\n"; 1698 | } 1699 | 1700 | $started = true; 1701 | $request .= $line; 1702 | } 1703 | 1704 | $env['CONTENT_LENGTH'] = strlen($request); 1705 | $env['REQUEST_METHOD'] = 'PUT'; 1706 | 1707 | if (empty($request)) { 1708 | junit_mark_test_as('BORK', $shortname, $tested, null, 'empty $request'); 1709 | return 'BORKED'; 1710 | } 1711 | 1712 | save_text($tmp_post, $request); 1713 | $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 1714 | 1715 | } else if (array_key_exists('POST', $section_text) && !empty($section_text['POST'])) { 1716 | 1717 | $post = trim($section_text['POST']); 1718 | $content_length = strlen($post); 1719 | save_text($tmp_post, $post); 1720 | 1721 | $env['REQUEST_METHOD'] = 'POST'; 1722 | if (empty($env['CONTENT_TYPE'])) { 1723 | $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; 1724 | } 1725 | 1726 | if (empty($env['CONTENT_LENGTH'])) { 1727 | $env['CONTENT_LENGTH'] = $content_length; 1728 | } 1729 | 1730 | $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 1731 | 1732 | } else if (array_key_exists('GZIP_POST', $section_text) && !empty($section_text['GZIP_POST'])) { 1733 | 1734 | $post = trim($section_text['GZIP_POST']); 1735 | $post = gzencode($post, 9, FORCE_GZIP); 1736 | $env['HTTP_CONTENT_ENCODING'] = 'gzip'; 1737 | 1738 | save_text($tmp_post, $post); 1739 | $content_length = strlen($post); 1740 | 1741 | $env['REQUEST_METHOD'] = 'POST'; 1742 | $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; 1743 | $env['CONTENT_LENGTH'] = $content_length; 1744 | 1745 | $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 1746 | 1747 | } else if (array_key_exists('DEFLATE_POST', $section_text) && !empty($section_text['DEFLATE_POST'])) { 1748 | $post = trim($section_text['DEFLATE_POST']); 1749 | $post = gzcompress($post, 9); 1750 | $env['HTTP_CONTENT_ENCODING'] = 'deflate'; 1751 | save_text($tmp_post, $post); 1752 | $content_length = strlen($post); 1753 | 1754 | $env['REQUEST_METHOD'] = 'POST'; 1755 | $env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; 1756 | $env['CONTENT_LENGTH'] = $content_length; 1757 | 1758 | $cmd = "$php $pass_options $ini_settings -f \"$test_file\"$cmdRedirect < \"$tmp_post\""; 1759 | 1760 | } else { 1761 | 1762 | $env['REQUEST_METHOD'] = 'GET'; 1763 | $env['CONTENT_TYPE'] = ''; 1764 | $env['CONTENT_LENGTH'] = ''; 1765 | 1766 | $cmd = "$php $pass_options $ini_settings -f \"$test_file\" $args$cmdRedirect"; 1767 | } 1768 | 1769 | if ($leak_check) { 1770 | $env['USE_ZEND_ALLOC'] = '0'; 1771 | $env['ZEND_DONT_UNLOAD_MODULES'] = 1; 1772 | 1773 | $valgrind_cmd = "valgrind -q --tool=memcheck --trace-children=yes"; 1774 | if (strpos($test_file, "pcre") !== false) { 1775 | $valgrind_cmd .= " --smc-check=all"; 1776 | } 1777 | 1778 | /* --vex-iropt-register-updates=allregs-at-mem-access is necessary for phpdbg watchpoint tests */ 1779 | if (version_compare($valgrind_version, '3.8.0', '>=')) { 1780 | /* valgrind 3.3.0+ doesn't have --log-file-exactly option */ 1781 | $cmd = "$valgrind_cmd --vex-iropt-register-updates=allregs-at-mem-access --log-file=$memcheck_filename $cmd"; 1782 | } elseif (version_compare($valgrind_version, '3.3.0', '>=')) { 1783 | $cmd = "$valgrind_cmd --vex-iropt-precise-memory-exns=yes --log-file=$memcheck_filename $cmd"; 1784 | } else { 1785 | $cmd = "$valgrind_cmd --vex-iropt-precise-memory-exns=yes --log-file-exactly=$memcheck_filename $cmd"; 1786 | } 1787 | 1788 | } else { 1789 | $env['USE_ZEND_ALLOC'] = '1'; 1790 | $env['ZEND_DONT_UNLOAD_MODULES'] = 0; 1791 | } 1792 | 1793 | if ($DETAILED) echo " 1794 | CONTENT_LENGTH = " . $env['CONTENT_LENGTH'] . " 1795 | CONTENT_TYPE = " . $env['CONTENT_TYPE'] . " 1796 | PATH_TRANSLATED = " . $env['PATH_TRANSLATED'] . " 1797 | QUERY_STRING = " . $env['QUERY_STRING'] . " 1798 | REDIRECT_STATUS = " . $env['REDIRECT_STATUS'] . " 1799 | REQUEST_METHOD = " . $env['REQUEST_METHOD'] . " 1800 | SCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . " 1801 | HTTP_COOKIE = " . $env['HTTP_COOKIE'] . " 1802 | COMMAND $cmd 1803 | "; 1804 | 1805 | junit_start_timer($shortname); 1806 | $startTime = microtime(true); 1807 | 1808 | $out = system_with_timeout($cmd, $env, isset($section_text['STDIN']) ? $section_text['STDIN'] : null, $captureStdIn, $captureStdOut, $captureStdErr); 1809 | 1810 | junit_finish_timer($shortname); 1811 | $time = microtime(true) - $startTime; 1812 | if ($time * 1000 >= $slow_min_ms) { 1813 | $PHP_FAILED_TESTS['SLOW'][] = array( 1814 | 'name' => $file, 1815 | 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 1816 | 'output' => '', 1817 | 'diff' => '', 1818 | 'info' => $time, 1819 | ); 1820 | } 1821 | 1822 | if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) { 1823 | 1824 | if (trim($section_text['CLEAN'])) { 1825 | show_file_block('clean', $section_text['CLEAN']); 1826 | save_text($test_clean, trim($section_text['CLEAN']), $temp_clean); 1827 | 1828 | if (!$no_clean) { 1829 | $clean_params = array(); 1830 | settings2array($ini_overwrites, $clean_params); 1831 | settings2params($clean_params); 1832 | $extra = substr(PHP_OS, 0, 3) !== "WIN" ? 1833 | "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;": ""; 1834 | system_with_timeout("$extra $php $pass_options -q $clean_params $no_file_cache \"$test_clean\"", $env); 1835 | } 1836 | 1837 | if (!$cfg['keep']['clean']) { 1838 | @unlink($test_clean); 1839 | } 1840 | } 1841 | } 1842 | 1843 | @unlink($tmp_post); 1844 | 1845 | $leaked = false; 1846 | $passed = false; 1847 | 1848 | if ($leak_check) { // leak check 1849 | $leaked = filesize($memcheck_filename) > 0; 1850 | 1851 | if (!$leaked) { 1852 | @unlink($memcheck_filename); 1853 | } 1854 | } 1855 | 1856 | // Does the output match what is expected? 1857 | $output = preg_replace("/\r\n/", "\n", trim($out)); 1858 | 1859 | /* when using CGI, strip the headers from the output */ 1860 | $headers = array(); 1861 | 1862 | if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) { 1863 | $output = trim($match[2]); 1864 | $rh = preg_split("/[\n\r]+/", $match[1]); 1865 | 1866 | foreach ($rh as $line) { 1867 | if (strpos($line, ':') !== false) { 1868 | $line = explode(':', $line, 2); 1869 | $headers[trim($line[0])] = trim($line[1]); 1870 | } 1871 | } 1872 | } 1873 | 1874 | $failed_headers = false; 1875 | 1876 | if (isset($section_text['EXPECTHEADERS'])) { 1877 | $want = array(); 1878 | $wanted_headers = array(); 1879 | $lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']); 1880 | 1881 | foreach($lines as $line) { 1882 | if (strpos($line, ':') !== false) { 1883 | $line = explode(':', $line, 2); 1884 | $want[trim($line[0])] = trim($line[1]); 1885 | $wanted_headers[] = trim($line[0]) . ': ' . trim($line[1]); 1886 | } 1887 | } 1888 | 1889 | $output_headers = array(); 1890 | 1891 | foreach($want as $k => $v) { 1892 | 1893 | if (isset($headers[$k])) { 1894 | $output_headers[] = $k . ': ' . $headers[$k]; 1895 | } 1896 | 1897 | if (!isset($headers[$k]) || $headers[$k] != $v) { 1898 | $failed_headers = true; 1899 | } 1900 | } 1901 | 1902 | ksort($wanted_headers); 1903 | $wanted_headers = implode("\n", $wanted_headers); 1904 | ksort($output_headers); 1905 | $output_headers = implode("\n", $output_headers); 1906 | } 1907 | 1908 | show_file_block('out', $output); 1909 | 1910 | if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { 1911 | 1912 | if (isset($section_text['EXPECTF'])) { 1913 | $wanted = trim($section_text['EXPECTF']); 1914 | } else { 1915 | $wanted = trim($section_text['EXPECTREGEX']); 1916 | } 1917 | 1918 | show_file_block('exp', $wanted); 1919 | $wanted_re = preg_replace('/\r\n/', "\n", $wanted); 1920 | 1921 | if (isset($section_text['EXPECTF'])) { 1922 | 1923 | // do preg_quote, but miss out any %r delimited sections 1924 | $temp = ""; 1925 | $r = "%r"; 1926 | $startOffset = 0; 1927 | $length = strlen($wanted_re); 1928 | while($startOffset < $length) { 1929 | $start = strpos($wanted_re, $r, $startOffset); 1930 | if ($start !== false) { 1931 | // we have found a start tag 1932 | $end = strpos($wanted_re, $r, $start+2); 1933 | if ($end === false) { 1934 | // unbalanced tag, ignore it. 1935 | $end = $start = $length; 1936 | } 1937 | } else { 1938 | // no more %r sections 1939 | $start = $end = $length; 1940 | } 1941 | // quote a non re portion of the string 1942 | $temp = $temp . preg_quote(substr($wanted_re, $startOffset, ($start - $startOffset)), '/'); 1943 | // add the re unquoted. 1944 | if ($end > $start) { 1945 | $temp = $temp . '(' . substr($wanted_re, $start+2, ($end - $start-2)). ')'; 1946 | } 1947 | $startOffset = $end + 2; 1948 | } 1949 | $wanted_re = $temp; 1950 | 1951 | $wanted_re = str_replace( 1952 | array('%binary_string_optional%'), 1953 | 'string', 1954 | $wanted_re 1955 | ); 1956 | $wanted_re = str_replace( 1957 | array('%unicode_string_optional%'), 1958 | 'string', 1959 | $wanted_re 1960 | ); 1961 | $wanted_re = str_replace( 1962 | array('%unicode\|string%', '%string\|unicode%'), 1963 | 'string', 1964 | $wanted_re 1965 | ); 1966 | $wanted_re = str_replace( 1967 | array('%u\|b%', '%b\|u%'), 1968 | '', 1969 | $wanted_re 1970 | ); 1971 | // Stick to basics 1972 | $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re); 1973 | $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re); 1974 | $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re); 1975 | $wanted_re = str_replace('%a', '.+', $wanted_re); 1976 | $wanted_re = str_replace('%A', '.*', $wanted_re); 1977 | $wanted_re = str_replace('%w', '\s*', $wanted_re); 1978 | $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re); 1979 | $wanted_re = str_replace('%d', '\d+', $wanted_re); 1980 | $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re); 1981 | $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re); 1982 | $wanted_re = str_replace('%c', '.', $wanted_re); 1983 | // %f allows two points "-.0.0" but that is the best *simple* expression 1984 | } 1985 | /* DEBUG YOUR REGEX HERE 1986 | var_dump($wanted_re); 1987 | print(str_repeat('=', 80) . "\n"); 1988 | var_dump($output); 1989 | */ 1990 | if (preg_match("/^$wanted_re\$/s", $output)) { 1991 | $passed = true; 1992 | if (!$cfg['keep']['php']) { 1993 | @unlink($test_file); 1994 | } 1995 | 1996 | if (!$leaked && !$failed_headers) { 1997 | if (isset($section_text['XFAIL'] )) { 1998 | $warn = true; 1999 | $info = " (warn: XFAIL section but test passes)"; 2000 | }else { 2001 | show_result("PASS", $tested, $tested_file, '', $temp_filenames); 2002 | junit_mark_test_as('PASS', $shortname, $tested); 2003 | return 'PASSED'; 2004 | } 2005 | } 2006 | } 2007 | 2008 | } else { 2009 | 2010 | $wanted = trim($section_text['EXPECT']); 2011 | $wanted = preg_replace('/\r\n/',"\n", $wanted); 2012 | show_file_block('exp', $wanted); 2013 | 2014 | // compare and leave on success 2015 | if (!strcmp($output, $wanted)) { 2016 | $passed = true; 2017 | 2018 | if (!$cfg['keep']['php']) { 2019 | @unlink($test_file); 2020 | } 2021 | 2022 | if (!$leaked && !$failed_headers) { 2023 | if (isset($section_text['XFAIL'] )) { 2024 | $warn = true; 2025 | $info = " (warn: XFAIL section but test passes)"; 2026 | }else { 2027 | show_result("PASS", $tested, $tested_file, '', $temp_filenames); 2028 | junit_mark_test_as('PASS', $shortname, $tested); 2029 | return 'PASSED'; 2030 | } 2031 | } 2032 | } 2033 | 2034 | $wanted_re = null; 2035 | } 2036 | 2037 | // Test failed so we need to report details. 2038 | if ($failed_headers) { 2039 | $passed = false; 2040 | $wanted = $wanted_headers . "\n--HEADERS--\n" . $wanted; 2041 | $output = $output_headers . "\n--HEADERS--\n" . $output; 2042 | 2043 | if (isset($wanted_re)) { 2044 | $wanted_re = preg_quote($wanted_headers . "\n--HEADERS--\n", '/') . $wanted_re; 2045 | } 2046 | } 2047 | 2048 | if ($leaked) { 2049 | $restype[] = 'LEAK'; 2050 | } 2051 | 2052 | if ($warn) { 2053 | $restype[] = 'WARN'; 2054 | } 2055 | 2056 | if (!$passed) { 2057 | if (isset($section_text['XFAIL'])) { 2058 | $restype[] = 'XFAIL'; 2059 | $info = ' XFAIL REASON: ' . rtrim($section_text['XFAIL']); 2060 | } else { 2061 | $restype[] = 'FAIL'; 2062 | } 2063 | } 2064 | 2065 | if (!$passed) { 2066 | 2067 | // write .exp 2068 | if (strpos($log_format, 'E') !== false && file_put_contents($exp_filename, $wanted, FILE_BINARY) === false) { 2069 | error("Cannot create expected test output - $exp_filename"); 2070 | } 2071 | 2072 | // write .out 2073 | if (strpos($log_format, 'O') !== false && file_put_contents($output_filename, $output, FILE_BINARY) === false) { 2074 | error("Cannot create test output - $output_filename"); 2075 | } 2076 | 2077 | // write .diff 2078 | $diff = generate_diff($wanted, $wanted_re, $output); 2079 | if (is_array($IN_REDIRECT)) { 2080 | $diff = "# original source file: $shortname\n" . $diff; 2081 | } 2082 | show_file_block('diff', $diff); 2083 | if (strpos($log_format, 'D') !== false && file_put_contents($diff_filename, $diff, FILE_BINARY) === false) { 2084 | error("Cannot create test diff - $diff_filename"); 2085 | } 2086 | 2087 | // write .sh 2088 | if (strpos($log_format, 'S') !== false && file_put_contents($sh_filename, "#!/bin/sh 2089 | 2090 | {$cmd} 2091 | ", FILE_BINARY) === false) { 2092 | error("Cannot create test shell script - $sh_filename"); 2093 | } 2094 | chmod($sh_filename, 0755); 2095 | 2096 | // write .log 2097 | if (strpos($log_format, 'L') !== false && file_put_contents($log_filename, " 2098 | ---- EXPECTED OUTPUT 2099 | $wanted 2100 | ---- ACTUAL OUTPUT 2101 | $output 2102 | ---- FAILED 2103 | ", FILE_BINARY) === false) { 2104 | error("Cannot create test log - $log_filename"); 2105 | error_report($file, $log_filename, $tested); 2106 | } 2107 | } 2108 | 2109 | show_result(implode('&', $restype), $tested, $tested_file, $info, $temp_filenames); 2110 | 2111 | foreach ($restype as $type) { 2112 | $PHP_FAILED_TESTS[$type.'ED'][] = array ( 2113 | 'name' => $file, 2114 | 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", 2115 | 'output' => $output_filename, 2116 | 'diff' => $diff_filename, 2117 | 'info' => $info, 2118 | ); 2119 | } 2120 | 2121 | $diff = empty($diff) ? '' : preg_replace('/\e/', '', $diff); 2122 | 2123 | junit_mark_test_as($restype, str_replace($cwd . '/', '', $tested_file), $tested, null, $info, $diff); 2124 | 2125 | return $restype[0] . 'ED'; 2126 | } 2127 | 2128 | function comp_line($l1, $l2, $is_reg) 2129 | { 2130 | if ($is_reg) { 2131 | return preg_match('/^'. $l1 . '$/s', $l2); 2132 | } else { 2133 | return !strcmp($l1, $l2); 2134 | } 2135 | } 2136 | 2137 | function count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2, $cnt1, $cnt2, $steps) 2138 | { 2139 | $equal = 0; 2140 | 2141 | while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { 2142 | $idx1++; 2143 | $idx2++; 2144 | $equal++; 2145 | $steps--; 2146 | } 2147 | if (--$steps > 0) { 2148 | $eq1 = 0; 2149 | $st = $steps / 2; 2150 | 2151 | for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) { 2152 | $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1, $cnt2, $st); 2153 | 2154 | if ($eq > $eq1) { 2155 | $eq1 = $eq; 2156 | } 2157 | } 2158 | 2159 | $eq2 = 0; 2160 | $st = $steps; 2161 | 2162 | for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) { 2163 | $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st); 2164 | if ($eq > $eq2) { 2165 | $eq2 = $eq; 2166 | } 2167 | } 2168 | 2169 | if ($eq1 > $eq2) { 2170 | $equal += $eq1; 2171 | } else if ($eq2 > 0) { 2172 | $equal += $eq2; 2173 | } 2174 | } 2175 | 2176 | return $equal; 2177 | } 2178 | 2179 | function generate_array_diff($ar1, $ar2, $is_reg, $w) 2180 | { 2181 | $idx1 = 0; $cnt1 = @count($ar1); 2182 | $idx2 = 0; $cnt2 = @count($ar2); 2183 | $diff = array(); 2184 | $old1 = array(); 2185 | $old2 = array(); 2186 | 2187 | while ($idx1 < $cnt1 && $idx2 < $cnt2) { 2188 | 2189 | if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { 2190 | $idx1++; 2191 | $idx2++; 2192 | continue; 2193 | } else { 2194 | 2195 | $c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1+1, $idx2, $cnt1, $cnt2, 10); 2196 | $c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2+1, $cnt1, $cnt2, 10); 2197 | 2198 | if ($c1 > $c2) { 2199 | $old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++]; 2200 | } else if ($c2 > 0) { 2201 | $old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++]; 2202 | } else { 2203 | $old1[$idx1] = sprintf("%03d- ", $idx1+1) . $w[$idx1++]; 2204 | $old2[$idx2] = sprintf("%03d+ ", $idx2+1) . $ar2[$idx2++]; 2205 | } 2206 | } 2207 | } 2208 | 2209 | reset($old1); $k1 = key($old1); $l1 = -2; 2210 | reset($old2); $k2 = key($old2); $l2 = -2; 2211 | 2212 | while ($k1 !== null || $k2 !== null) { 2213 | 2214 | if ($k1 == $l1 + 1 || $k2 === null) { 2215 | $l1 = $k1; 2216 | $diff[] = current($old1); 2217 | $k1 = next($old1) ? key($old1) : null; 2218 | } else if ($k2 == $l2 + 1 || $k1 === null) { 2219 | $l2 = $k2; 2220 | $diff[] = current($old2); 2221 | $k2 = next($old2) ? key($old2) : null; 2222 | } else if ($k1 < $k2) { 2223 | $l1 = $k1; 2224 | $diff[] = current($old1); 2225 | $k1 = next($old1) ? key($old1) : null; 2226 | } else { 2227 | $l2 = $k2; 2228 | $diff[] = current($old2); 2229 | $k2 = next($old2) ? key($old2) : null; 2230 | } 2231 | } 2232 | 2233 | while ($idx1 < $cnt1) { 2234 | $diff[] = sprintf("%03d- ", $idx1 + 1) . $w[$idx1++]; 2235 | } 2236 | 2237 | while ($idx2 < $cnt2) { 2238 | $diff[] = sprintf("%03d+ ", $idx2 + 1) . $ar2[$idx2++]; 2239 | } 2240 | 2241 | return $diff; 2242 | } 2243 | 2244 | function generate_diff($wanted, $wanted_re, $output) 2245 | { 2246 | $w = explode("\n", $wanted); 2247 | $o = explode("\n", $output); 2248 | $r = is_null($wanted_re) ? $w : explode("\n", $wanted_re); 2249 | $diff = generate_array_diff($r, $o, !is_null($wanted_re), $w); 2250 | 2251 | return implode("\r\n", $diff); 2252 | } 2253 | 2254 | function error($message) 2255 | { 2256 | echo "ERROR: {$message}\n"; 2257 | exit(1); 2258 | } 2259 | 2260 | function settings2array($settings, &$ini_settings) 2261 | { 2262 | foreach($settings as $setting) { 2263 | 2264 | if (strpos($setting, '=') !== false) { 2265 | $setting = explode("=", $setting, 2); 2266 | $name = trim($setting[0]); 2267 | $value = trim($setting[1]); 2268 | 2269 | if ($name == 'extension' || $name == 'zend_extension') { 2270 | 2271 | if (!isset($ini_settings[$name])) { 2272 | $ini_settings[$name] = array(); 2273 | } 2274 | 2275 | $ini_settings[$name][] = $value; 2276 | 2277 | } else { 2278 | $ini_settings[$name] = $value; 2279 | } 2280 | } 2281 | } 2282 | } 2283 | 2284 | function settings2params(&$ini_settings) 2285 | { 2286 | $settings = ''; 2287 | 2288 | foreach($ini_settings as $name => $value) { 2289 | 2290 | if (is_array($value)) { 2291 | foreach($value as $val) { 2292 | $val = addslashes($val); 2293 | $settings .= " -d \"$name=$val\""; 2294 | } 2295 | } else { 2296 | if (substr(PHP_OS, 0, 3) == "WIN" && !empty($value) && $value{0} == '"') { 2297 | $len = strlen($value); 2298 | 2299 | if ($value{$len - 1} == '"') { 2300 | $value{0} = "'"; 2301 | $value{$len - 1} = "'"; 2302 | } 2303 | } else { 2304 | $value = addslashes($value); 2305 | } 2306 | 2307 | $settings .= " -d \"$name=$value\""; 2308 | } 2309 | } 2310 | 2311 | $ini_settings = $settings; 2312 | } 2313 | 2314 | function compute_summary() 2315 | { 2316 | global $n_total, $test_results, $ignored_by_ext, $sum_results, $percent_results; 2317 | 2318 | $n_total = count($test_results); 2319 | $n_total += $ignored_by_ext; 2320 | $sum_results = array( 2321 | 'PASSED' => 0, 2322 | 'WARNED' => 0, 2323 | 'SKIPPED' => 0, 2324 | 'FAILED' => 0, 2325 | 'BORKED' => 0, 2326 | 'LEAKED' => 0, 2327 | 'XFAILED' => 0 2328 | ); 2329 | 2330 | foreach ($test_results as $v) { 2331 | $sum_results[$v]++; 2332 | } 2333 | 2334 | $sum_results['SKIPPED'] += $ignored_by_ext; 2335 | $percent_results = array(); 2336 | 2337 | foreach ($sum_results as $v => $n) { 2338 | $percent_results[$v] = (100.0 * $n) / $n_total; 2339 | } 2340 | } 2341 | 2342 | function get_summary($show_ext_summary, $show_html) 2343 | { 2344 | global $exts_skipped, $exts_tested, $n_total, $sum_results, $percent_results, $end_time, $start_time, $failed_test_summary, $PHP_FAILED_TESTS, $leak_check; 2345 | 2346 | $x_total = $n_total - $sum_results['SKIPPED'] - $sum_results['BORKED']; 2347 | 2348 | if ($x_total) { 2349 | $x_warned = (100.0 * $sum_results['WARNED']) / $x_total; 2350 | $x_failed = (100.0 * $sum_results['FAILED']) / $x_total; 2351 | $x_xfailed = (100.0 * $sum_results['XFAILED']) / $x_total; 2352 | $x_leaked = (100.0 * $sum_results['LEAKED']) / $x_total; 2353 | $x_passed = (100.0 * $sum_results['PASSED']) / $x_total; 2354 | } else { 2355 | $x_warned = $x_failed = $x_passed = $x_leaked = $x_xfailed = 0; 2356 | } 2357 | 2358 | $summary = ''; 2359 | 2360 | if ($show_html) { 2361 | $summary .= "
\n";
2362 |   }
2363 | 
2364 |   if ($show_ext_summary) {
2365 |     $summary .= '
2366 | =====================================================================
2367 | TEST RESULT SUMMARY
2368 | ---------------------------------------------------------------------
2369 | Exts skipped    : ' . sprintf('%4d', $exts_skipped) . '
2370 | Exts tested     : ' . sprintf('%4d', $exts_tested) . '
2371 | ---------------------------------------------------------------------
2372 | ';
2373 |   }
2374 | 
2375 |   $summary .= '
2376 | Number of tests : ' . sprintf('%4d', $n_total) . '          ' . sprintf('%8d', $x_total);
2377 | 
2378 |   if ($sum_results['BORKED']) {
2379 |     $summary .= '
2380 | Tests borked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------';
2381 |   }
2382 | 
2383 |   $summary .= '
2384 | Tests skipped   : ' . sprintf('%4d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' --------
2385 | Tests warned    : ' . sprintf('%4d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . '
2386 | Tests failed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed) . '
2387 | Expected fail   : ' . sprintf('%4d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed);
2388 | 
2389 |   if ($leak_check) {
2390 |     $summary .= '
2391 | Tests leaked    : ' . sprintf('%4d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked);
2392 |   }
2393 | 
2394 |   $summary .= '
2395 | Tests passed    : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . '
2396 | ---------------------------------------------------------------------
2397 | Time taken      : ' . sprintf('%4d seconds', $end_time - $start_time) . '
2398 | =====================================================================
2399 | ';
2400 |   $failed_test_summary = '';
2401 | 
2402 |   if (count($PHP_FAILED_TESTS['SLOW'])) {
2403 |     usort($PHP_FAILED_TESTS['SLOW'], function($a, $b) {
2404 |       return $a['info'] < $b['info'] ? 1 : -1;
2405 |     });
2406 | 
2407 |     $failed_test_summary .= '
2408 | =====================================================================
2409 | SLOW TEST SUMMARY
2410 | ---------------------------------------------------------------------
2411 | ';
2412 |     foreach ($PHP_FAILED_TESTS['SLOW'] as $failed_test_data) {
2413 |       $failed_test_summary .= sprintf('(%.3f s) ', $failed_test_data['info']) . $failed_test_data['test_name'] . "\n";
2414 |     }
2415 |     $failed_test_summary .=  "=====================================================================\n";
2416 |   }
2417 | 
2418 |   if (count($PHP_FAILED_TESTS['XFAILED'])) {
2419 |     $failed_test_summary .= '
2420 | =====================================================================
2421 | EXPECTED FAILED TEST SUMMARY
2422 | ---------------------------------------------------------------------
2423 | ';
2424 |     foreach ($PHP_FAILED_TESTS['XFAILED'] as $failed_test_data) {
2425 |       $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2426 |     }
2427 |     $failed_test_summary .=  "=====================================================================\n";
2428 |   }
2429 | 
2430 |   if (count($PHP_FAILED_TESTS['BORKED'])) {
2431 |     $failed_test_summary .= '
2432 | =====================================================================
2433 | BORKED TEST SUMMARY
2434 | ---------------------------------------------------------------------
2435 | ';
2436 |     foreach ($PHP_FAILED_TESTS['BORKED'] as $failed_test_data) {
2437 |       $failed_test_summary .= $failed_test_data['info'] . "\n";
2438 |     }
2439 | 
2440 |     $failed_test_summary .=  "=====================================================================\n";
2441 |   }
2442 | 
2443 |   if (count($PHP_FAILED_TESTS['FAILED'])) {
2444 |     $failed_test_summary .= '
2445 | =====================================================================
2446 | FAILED TEST SUMMARY
2447 | ---------------------------------------------------------------------
2448 | ';
2449 |     foreach ($PHP_FAILED_TESTS['FAILED'] as $failed_test_data) {
2450 |       $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2451 |     }
2452 |     $failed_test_summary .=  "=====================================================================\n";
2453 |   }
2454 |   if (count($PHP_FAILED_TESTS['WARNED'])) {
2455 |     $failed_test_summary .= '
2456 | =====================================================================
2457 | WARNED TEST SUMMARY
2458 | ---------------------------------------------------------------------
2459 | ';
2460 |     foreach ($PHP_FAILED_TESTS['WARNED'] as $failed_test_data) {
2461 |       $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2462 |     }
2463 | 
2464 |     $failed_test_summary .=  "=====================================================================\n";
2465 |   }
2466 | 
2467 |   if (count($PHP_FAILED_TESTS['LEAKED'])) {
2468 |     $failed_test_summary .= '
2469 | =====================================================================
2470 | LEAKED TEST SUMMARY
2471 | ---------------------------------------------------------------------
2472 | ';
2473 |     foreach ($PHP_FAILED_TESTS['LEAKED'] as $failed_test_data) {
2474 |       $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n";
2475 |     }
2476 | 
2477 |     $failed_test_summary .=  "=====================================================================\n";
2478 |   }
2479 | 
2480 |   if ($failed_test_summary && !getenv('NO_PHPTEST_SUMMARY')) {
2481 |     $summary .= $failed_test_summary;
2482 |   }
2483 | 
2484 |   if ($show_html) {
2485 |     $summary .= "
"; 2486 | } 2487 | 2488 | return $summary; 2489 | } 2490 | 2491 | function show_start($start_time) 2492 | { 2493 | global $html_output, $html_file; 2494 | 2495 | if ($html_output) { 2496 | fwrite($html_file, "

Time Start: " . date('Y-m-d H:i:s', $start_time) . "

\n"); 2497 | fwrite($html_file, "\n"); 2498 | } 2499 | 2500 | echo "TIME START " . date('Y-m-d H:i:s', $start_time) . "\n=====================================================================\n"; 2501 | } 2502 | 2503 | function show_end($end_time) 2504 | { 2505 | global $html_output, $html_file; 2506 | 2507 | if ($html_output) { 2508 | fwrite($html_file, "
\n"); 2509 | fwrite($html_file, "

Time End: " . date('Y-m-d H:i:s', $end_time) . "

\n"); 2510 | } 2511 | 2512 | echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $end_time) . "\n"; 2513 | } 2514 | 2515 | function show_summary() 2516 | { 2517 | global $html_output, $html_file; 2518 | 2519 | if ($html_output) { 2520 | fwrite($html_file, "
\n" . get_summary(true, true)); 2521 | } 2522 | 2523 | echo get_summary(true, false); 2524 | } 2525 | 2526 | function show_redirect_start($tests, $tested, $tested_file) 2527 | { 2528 | global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS; 2529 | 2530 | if ($html_output) { 2531 | fwrite($html_file, "---> $tests ($tested [$tested_file]) begin\n"); 2532 | } 2533 | 2534 | if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { 2535 | echo "REDIRECT $tests ($tested [$tested_file]) begin\n"; 2536 | } else { 2537 | // Write over the last line to avoid random trailing chars on next echo 2538 | echo str_repeat(" ", $line_length), "\r"; 2539 | } 2540 | } 2541 | 2542 | function show_redirect_ends($tests, $tested, $tested_file) 2543 | { 2544 | global $html_output, $html_file, $line_length, $SHOW_ONLY_GROUPS; 2545 | 2546 | if ($html_output) { 2547 | fwrite($html_file, "---> $tests ($tested [$tested_file]) done\n"); 2548 | } 2549 | 2550 | if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { 2551 | echo "REDIRECT $tests ($tested [$tested_file]) done\n"; 2552 | } else { 2553 | // Write over the last line to avoid random trailing chars on next echo 2554 | echo str_repeat(" ", $line_length), "\r"; 2555 | } 2556 | } 2557 | 2558 | function show_test($test_idx, $shortname) 2559 | { 2560 | global $test_cnt; 2561 | global $line_length; 2562 | 2563 | $str = "TEST $test_idx/$test_cnt [$shortname]\r"; 2564 | $line_length = strlen($str); 2565 | echo $str; 2566 | flush(); 2567 | } 2568 | 2569 | function show_result($result, $tested, $tested_file, $extra = '', $temp_filenames = null) 2570 | { 2571 | global $html_output, $html_file, $temp_target, $temp_urlbase, $line_length, $SHOW_ONLY_GROUPS; 2572 | 2573 | if (!$SHOW_ONLY_GROUPS || in_array($result, $SHOW_ONLY_GROUPS)) { 2574 | echo "$result $tested [$tested_file] $extra\n"; 2575 | } else if (!$SHOW_ONLY_GROUPS) { 2576 | // Write over the last line to avoid random trailing chars on next echo 2577 | echo str_repeat(" ", $line_length), "\r"; 2578 | } 2579 | 2580 | if ($html_output) { 2581 | 2582 | if (isset($temp_filenames['file']) && file_exists($temp_filenames['file'])) { 2583 | $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['file']); 2584 | $tested = "$tested"; 2585 | } 2586 | 2587 | if (isset($temp_filenames['skip']) && file_exists($temp_filenames['skip'])) { 2588 | 2589 | if (empty($extra)) { 2590 | $extra = "skipif"; 2591 | } 2592 | 2593 | $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['skip']); 2594 | $extra = "$extra"; 2595 | 2596 | } else if (empty($extra)) { 2597 | $extra = " "; 2598 | } 2599 | 2600 | if (isset($temp_filenames['diff']) && file_exists($temp_filenames['diff'])) { 2601 | $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['diff']); 2602 | $diff = "diff"; 2603 | } else { 2604 | $diff = " "; 2605 | } 2606 | 2607 | if (isset($temp_filenames['mem']) && file_exists($temp_filenames['mem'])) { 2608 | $url = str_replace($temp_target, $temp_urlbase, $temp_filenames['mem']); 2609 | $mem = "leaks"; 2610 | } else { 2611 | $mem = " "; 2612 | } 2613 | 2614 | fwrite($html_file, 2615 | "" . 2616 | "$result" . 2617 | "$tested" . 2618 | "$extra" . 2619 | "$diff" . 2620 | "$mem" . 2621 | "\n"); 2622 | } 2623 | } 2624 | 2625 | function junit_init() { 2626 | // Check whether a junit log is wanted. 2627 | $JUNIT = getenv('TEST_PHP_JUNIT'); 2628 | if (empty($JUNIT)) { 2629 | $JUNIT = FALSE; 2630 | } elseif (!$fp = fopen($JUNIT, 'w')) { 2631 | error("Failed to open $JUNIT for writing."); 2632 | } else { 2633 | $JUNIT = array( 2634 | 'fp' => $fp, 2635 | 'name' => 'php-src', 2636 | 'test_total' => 0, 2637 | 'test_pass' => 0, 2638 | 'test_fail' => 0, 2639 | 'test_error' => 0, 2640 | 'test_skip' => 0, 2641 | 'test_warn' => 0, 2642 | 'execution_time'=> 0, 2643 | 'suites' => array(), 2644 | 'files' => array() 2645 | ); 2646 | } 2647 | 2648 | $GLOBALS['JUNIT'] = $JUNIT; 2649 | } 2650 | 2651 | function junit_save_xml() { 2652 | global $JUNIT; 2653 | if (!junit_enabled()) return; 2654 | 2655 | $xml = ''. PHP_EOL . 2656 | '' . PHP_EOL; 2657 | $xml .= junit_get_suite_xml(); 2658 | $xml .= ''; 2659 | fwrite($JUNIT['fp'], $xml); 2660 | } 2661 | 2662 | function junit_get_suite_xml($suite_name = '') { 2663 | global $JUNIT; 2664 | 2665 | $suite = $suite_name ? $JUNIT['suites'][$suite_name] : $JUNIT; 2666 | 2667 | $result = sprintf( 2668 | '' . PHP_EOL, 2669 | $suite['name'], $suite['test_total'], $suite['test_fail'], $suite['test_error'], $suite['test_skip'], 2670 | $suite['execution_time'] 2671 | ); 2672 | 2673 | foreach($suite['suites'] as $sub_suite) { 2674 | $result .= junit_get_suite_xml($sub_suite['name']); 2675 | } 2676 | 2677 | // Output files only in subsuites 2678 | if (!empty($suite_name)) { 2679 | foreach($suite['files'] as $file) { 2680 | $result .= $JUNIT['files'][$file]['xml']; 2681 | } 2682 | } 2683 | 2684 | $result .= '' . PHP_EOL; 2685 | 2686 | return $result; 2687 | } 2688 | 2689 | function junit_enabled() { 2690 | global $JUNIT; 2691 | return !empty($JUNIT); 2692 | } 2693 | 2694 | /** 2695 | * @param array|string $type 2696 | * @param string $file_name 2697 | * @param string $test_name 2698 | * @param int|string $time 2699 | * @param string $message 2700 | * @param string $details 2701 | * @return void 2702 | */ 2703 | function junit_mark_test_as($type, $file_name, $test_name, $time = null, $message = '', $details = '') { 2704 | global $JUNIT; 2705 | if (!junit_enabled()) return; 2706 | 2707 | $suite = junit_get_suitename_for($file_name); 2708 | 2709 | junit_suite_record($suite, 'test_total'); 2710 | 2711 | $time = null !== $time ? $time : junit_get_timer($file_name); 2712 | junit_suite_record($suite, 'execution_time', $time); 2713 | 2714 | $escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8'); 2715 | $escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function ($c) { 2716 | return sprintf('[[0x%02x]]', ord($c[0])); 2717 | }, $escaped_details); 2718 | $escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); 2719 | 2720 | $escaped_test_name = basename($file_name) . ' - ' . htmlspecialchars($test_name, ENT_QUOTES); 2721 | $JUNIT['files'][$file_name]['xml'] = "\n"; 2722 | 2723 | if (is_array($type)) { 2724 | $output_type = $type[0] . 'ED'; 2725 | $temp = array_intersect(array('XFAIL', 'FAIL', 'WARN'), $type); 2726 | $type = reset($temp); 2727 | } else { 2728 | $output_type = $type . 'ED'; 2729 | } 2730 | 2731 | if ('PASS' == $type || 'XFAIL' == $type) { 2732 | junit_suite_record($suite, 'test_pass'); 2733 | } elseif ('BORK' == $type) { 2734 | junit_suite_record($suite, 'test_error'); 2735 | $JUNIT['files'][$file_name]['xml'] .= "\n"; 2736 | } elseif ('SKIP' == $type) { 2737 | junit_suite_record($suite, 'test_skip'); 2738 | $JUNIT['files'][$file_name]['xml'] .= "$escaped_message\n"; 2739 | } elseif ('WARN' == $type) { 2740 | junit_suite_record($suite, 'test_warn'); 2741 | $JUNIT['files'][$file_name]['xml'] .= "$escaped_message\n"; 2742 | } elseif ('FAIL' == $type) { 2743 | junit_suite_record($suite, 'test_fail'); 2744 | $JUNIT['files'][$file_name]['xml'] .= "$escaped_details\n"; 2745 | } else { 2746 | junit_suite_record($suite, 'test_error'); 2747 | $JUNIT['files'][$file_name]['xml'] .= "$escaped_details\n"; 2748 | } 2749 | 2750 | $JUNIT['files'][$file_name]['xml'] .= "\n"; 2751 | 2752 | } 2753 | 2754 | function junit_suite_record($suite, $param, $value = 1) { 2755 | global $JUNIT; 2756 | 2757 | $JUNIT[$param] += $value; 2758 | $JUNIT['suites'][$suite][$param] += $value; 2759 | } 2760 | 2761 | function junit_get_timer($file_name) { 2762 | global $JUNIT; 2763 | if (!junit_enabled()) return 0; 2764 | 2765 | if (isset($JUNIT['files'][$file_name]['total'])) { 2766 | return number_format($JUNIT['files'][$file_name]['total'], 4); 2767 | } 2768 | 2769 | return 0; 2770 | } 2771 | 2772 | function junit_start_timer($file_name) { 2773 | global $JUNIT; 2774 | if (!junit_enabled()) return; 2775 | 2776 | if (!isset($JUNIT['files'][$file_name]['start'])) { 2777 | $JUNIT['files'][$file_name]['start'] = microtime(true); 2778 | 2779 | $suite = junit_get_suitename_for($file_name); 2780 | junit_init_suite($suite); 2781 | $JUNIT['suites'][$suite]['files'][$file_name] = $file_name; 2782 | } 2783 | } 2784 | 2785 | function junit_get_suitename_for($file_name) { 2786 | return junit_path_to_classname(dirname($file_name)); 2787 | } 2788 | 2789 | function junit_path_to_classname($file_name) { 2790 | global $JUNIT; 2791 | return $JUNIT['name'] . '.' . str_replace(DIRECTORY_SEPARATOR, '.', $file_name); 2792 | } 2793 | 2794 | function junit_init_suite($suite_name) { 2795 | global $JUNIT; 2796 | if (!junit_enabled()) return; 2797 | 2798 | if (!empty($JUNIT['suites'][$suite_name])) { 2799 | return; 2800 | } 2801 | 2802 | $JUNIT['suites'][$suite_name] = array( 2803 | 'name' => $suite_name, 2804 | 'test_total' => 0, 2805 | 'test_pass' => 0, 2806 | 'test_fail' => 0, 2807 | 'test_error' => 0, 2808 | 'test_skip' => 0, 2809 | 'suites' => array(), 2810 | 'files' => array(), 2811 | 'execution_time'=> 0, 2812 | ); 2813 | } 2814 | 2815 | function junit_finish_timer($file_name) { 2816 | global $JUNIT; 2817 | if (!junit_enabled()) return; 2818 | 2819 | if (!isset($JUNIT['files'][$file_name]['start'])) { 2820 | error("Timer for $file_name was not started!"); 2821 | } 2822 | 2823 | if (!isset($JUNIT['files'][$file_name]['total'])) { 2824 | $JUNIT['files'][$file_name]['total'] = 0; 2825 | } 2826 | 2827 | $start = $JUNIT['files'][$file_name]['start']; 2828 | $JUNIT['files'][$file_name]['total'] += microtime(true) - $start; 2829 | unset($JUNIT['files'][$file_name]['start']); 2830 | } 2831 | 2832 | /* 2833 | * Local variables: 2834 | * tab-width: 4 2835 | * c-basic-offset: 4 2836 | * End: 2837 | * vim: noet sw=4 ts=4 2838 | */ 2839 | ?> 2840 | --------------------------------------------------------------------------------