Completed
Push — master ( 93a702...008e2c )
by Alexander
03:47
created

RemoteCoverageTool::init()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
ccs 0
cts 9
cp 0
rs 9.4285
cc 3
eloc 8
nc 4
nop 1
crap 12
1
<?php
2
/**
3
 * This file is part of the phpunit-mink library.
4
 * For the full copyright and license information, please view
5
 * the LICENSE file that was distributed with this source code.
6
 *
7
 * @copyright Alexander Obuhovich <[email protected]>
8
 * @link      https://github.com/aik099/phpunit-mink
9
 */
10
11
namespace aik099\PHPUnit\RemoteCoverage;
12
13
14
/* Include file from PEAR.
15
require_once 'File/Iterator/Autoload.php';
16
require_once 'PHP/CodeCoverage/Autoload.php';*/
17
18
class RemoteCoverageTool
19
{
20
21
	const TEST_ID_VARIABLE = 'PHPUNIT_MINK_TEST_ID';
22
23
	const DATA_DIRECTORY_VARIABLE = 'PHPUNIT_MINK_COVERAGE_DATA_DIRECTORY';
24
25
	/**
26
	 * Directory for coverage information collection.
27
	 *
28
	 * @var string
29
	 */
30
	protected $dataDirectory;
31
32
	/**
33
	 * Files, excluded from coverage collection.
34
	 *
35
	 * @var array
36
	 */
37
	protected $excludedFiles = array(__FILE__);
38
39
	/**
40
	 * Collects & reports coverage information.
41
	 *
42
	 * @param string|null $data_directory Directory for coverage information collection.
43
	 *
44
	 * @return void
45
	 */
46
	public static function init($data_directory = null)
47
	{
48
		$coverage_tool = new self($data_directory);
49
		$mode = isset($_GET['rct_mode']) ? $_GET['rct_mode'] : '';
50
51
		if ( $mode == 'output' ) {
52
			echo $coverage_tool->aggregateCoverageInformation();
53
		}
54
		else {
55
			$coverage_tool->startCollection();
56
			register_shutdown_function(array($coverage_tool, 'stopCollection'));
57
		}
58
	}
59
60
	/**
61
	 * Creates an instance of remove coverage tool.
62
	 *
63
	 * @param string|null $data_directory Directory for coverage information collection.
64
	 */
65
	public function __construct($data_directory = null)
66
	{
67
		if ( !isset($data_directory) ) {
68
			if ( isset($GLOBALS[self::DATA_DIRECTORY_VARIABLE]) ) {
69
				$this->dataDirectory = $this->assertDirectory($GLOBALS[self::DATA_DIRECTORY_VARIABLE]);
70
			}
71
			else {
72
				$this->dataDirectory = getcwd();
73
			}
74
		}
75
		else {
76
			$this->dataDirectory = $this->assertDirectory($data_directory);
77
		}
78
	}
79
80
	/**
81
	 * Checks that a directory is valid.
82
	 *
83
	 * @param string $directory Directory.
84
	 *
85
	 * @throws \InvalidArgumentException When directory is invalid.
86
	 * @return string
87
	 */
88
	protected function assertDirectory($directory)
89
	{
90
		if ( !is_string($directory) || !is_dir($directory) || !file_exists($directory) ) {
91
			throw new \InvalidArgumentException('Directory "' . $directory . '" is invalid');
92
		}
93
94
		return $directory;
95
	}
96
97
	/**
98
	 * Excludes a file from coverage.
99
	 *
100
	 * @param string $file Path to file, that needs to be excluded.
101
	 *
102
	 * @return void
103
	 */
104
	public function excludeFile($file)
105
	{
106
		$this->excludedFiles[] = $file;
107
	}
108
109
	/**
110
	 * Starts coverage information collection.
111
	 *
112
	 * @return void
113
	 */
114
	public function startCollection()
115
	{
116
		if ( !$this->enabled() ) {
117
			return;
118
		}
119
120
		xdebug_start_code_coverage(XDEBUG_CC_UNUSED | XDEBUG_CC_DEAD_CODE);
121
	}
122
123
	/**
124
	 * Stops coverage information collection.
125
	 *
126
	 * @return void
127
	 */
128
	public function stopCollection()
129
	{
130
		if ( !$this->enabled() ) {
131
			return;
132
		}
133
134
		$data = xdebug_get_code_coverage();
135
		xdebug_stop_code_coverage();
136
137
		foreach ( $this->excludedFiles as $file ) {
138
			unset($data[$file]);
139
		}
140
141
		$unique_id = md5(uniqid(rand(), true));
142
		file_put_contents(
143
			$name = $this->getStorageLocationPrefix() . '.' . $unique_id . '.' . $_COOKIE[self::TEST_ID_VARIABLE],
144
			serialize($data)
145
		);
146
	}
147
148
	/**
149
	 * Determines if coverage information collection can be started.
150
	 *
151
	 * @return string
152
	 * @throws \RuntimeException When Xdebug extension not enabled.
153
	 */
154
	protected function enabled()
155
	{
156
		if ( !extension_loaded('xdebug') ) {
157
			throw new \RuntimeException('Xdebug extension must be enabled for coverage collection');
158
		}
159
160
		return isset($_COOKIE[self::TEST_ID_VARIABLE]) && !isset($_GET[self::TEST_ID_VARIABLE]);
161
	}
162
163
	/**
164
	 * Returns name of the file, where coverage information will be stored.
165
	 *
166
	 * @return string
167
	 */
168
	protected function getStorageLocationPrefix()
169
	{
170
		return $this->dataDirectory . DIRECTORY_SEPARATOR . md5($_SERVER['SCRIPT_FILENAME']);
171
	}
172
173
	/**
174
	 * Aggregates previously collected coverage information.
175
	 *
176
	 * @return string
177
	 */
178
	public function aggregateCoverageInformation()
179
	{
180
		if ( !isset($_GET[self::TEST_ID_VARIABLE]) ) {
181
			return '';
182
		}
183
184
		$coverage = array();
185
		$filter = new \PHP_CodeCoverage_Filter();
186
187
		foreach ( $this->getDataDirectoryFiles() as $data_directory_file ) {
188
			$raw_coverage_data = unserialize(file_get_contents($data_directory_file));
189
190
			foreach ( $raw_coverage_data as $file => $lines ) {
191
				if ( !$filter->isFile($file) ) {
192
					continue;
193
				}
194
195
				if ( !isset($coverage[$file]) ) {
196
					$coverage[$file] = array('md5' => md5_file($file), 'coverage' => $lines);
197
				}
198
				else {
199
					foreach ( $lines as $line => $flag ) {
200
						if ( !isset($coverage[$file]['coverage'][$line])
201
							|| $flag > $coverage[$file]['coverage'][$line]
202
						) {
203
							$coverage[$file]['coverage'][$line] = $flag;
204
						}
205
					}
206
				}
207
			}
208
		}
209
210
		return serialize($coverage);
211
	}
212
213
	/**
214
	 * Returns contents of data directory for a current test.
215
	 *
216
	 * @return array
217
	 */
218
	protected function getDataDirectoryFiles()
219
	{
220
		$facade = new \File_Iterator_Facade();
221
222
		return $facade->getFilesAsArray($this->dataDirectory, $_GET[self::TEST_ID_VARIABLE]);
223
	}
224
225
}
226