mageekguy\atoum\report\fields\runner\coverage\html: lines coverage

84% of 1130

OPs

95% of 201

Lines

70% of 140

Branches

1% of 4124

Paths
Method OPs OPs % Lines Line % Branches Branches % Paths Path %
mageekguy\atoum\report\fields\runner\coverage\html::__construct() 43 100% 10 100% 1 0% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::__toString() 782 78% 138 92% 94 65% 4096 0%
mageekguy\atoum\report\fields\runner\coverage\html::setReflectionClassInjector() 34 97% 7 100% 4 75% 2 100%
mageekguy\atoum\report\fields\runner\coverage\html::getReflectionClass() 43 98% 9 100% 7 86% 3 100%
mageekguy\atoum\report\fields\runner\coverage\html::setProjectName() 10 100% 2 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::getProjectName() 6 100% 2 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::setDestinationDirectory() 10 100% 2 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::getDestinationDirectory() 6 100% 2 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::setUrlPrompt() 16 100% 2 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::getUrlPrompt() 6 100% 1 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::setUrlColorizer() 16 100% 2 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::getUrlColorizer() 6 100% 1 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::setTemplatesDirectory() 26 100% 2 100% 5 100% 2 100%
mageekguy\atoum\report\fields\runner\coverage\html::getTemplatesDirectory() 6 100% 1 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::setTemplateParser() 16 100% 2 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::getTemplateParser() 6 100% 2 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::setRootUrl() 10 100% 2 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::getRootUrl() 6 100% 1 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::getDestinationDirectoryIterator() 26 100% 1 100% 1 100% 1 100%
mageekguy\atoum\report\fields\runner\coverage\html::cleanDestinationDirectory() 56 82% 12 100% 15 60% 6 17%
#
1
<?php
2

                    
3
namespace mageekguy\atoum\report\fields\runner\coverage;
4

                    
5
require_once __DIR__ . '/../../../../../constants.php';
6

                    
7
use
8
	mageekguy\atoum,
9
	mageekguy\atoum\report,
10
	mageekguy\atoum\template,
11
	mageekguy\atoum\exceptions,
12
	mageekguy\atoum\cli\prompt,
13
	mageekguy\atoum\cli\colorizer
14
;
15

                    
16
class html extends report\fields\runner\coverage\cli
17
{
18
	const htmlExtensionFile = '.html';
19

                    
20
	protected $urlPrompt = null;
21
	protected $urlColorizer = null;
22
	protected $rootUrl = '';
23
	protected $projectName = '';
24
	protected $templatesDirectory = null;
25
	protected $destinationDirectory = null;
26
	protected $templateParser = null;
27
	protected $reflectionClassInjector = null;
28
	protected $bootstrapFile = null;
29

                    
30
	public function __construct($projectName, $destinationDirectory)100%
31
	{
32
		parent::__construct();
33

                    
34
		$this
35
			->setProjectName($projectName)
36
			->setDestinationDirectory($destinationDirectory)
37
			->setUrlPrompt()
38
			->setUrlColorizer()
39
			->setTemplatesDirectory()
40
			->setTemplateParser()
41
			->setRootUrl('/')
42
		;
43
	}
44

                    
45
	public function __toString()92%
46
	{
47
		$string = '';
48

                    
49
		if (sizeof($this->coverage) > 0)
50
		{
51
			try
52
			{
53
				$this->cleanDestinationDirectory();
54

                    
55
				$this->adapter->copy($this->templatesDirectory . DIRECTORY_SEPARATOR . 'screen.css', $this->destinationDirectory . DIRECTORY_SEPARATOR . 'screen.css');
56

                    
57
				$classes = $this->coverage->getClasses();
58

                    
59
				$indexTemplate = $this->templateParser->parseFile($this->templatesDirectory . DIRECTORY_SEPARATOR . 'index.tpl');
60
				$indexTemplate->projectName = $this->projectName;
61
				$indexTemplate->rootUrl = $this->rootUrl;
62

                    
63
				$coverageValue = $this->coverage->getValue();
64

                    
65
				if ($coverageValue === null)
66
				{
67
					$indexTemplate->coverageUnavailable->build();
68
				}
69
				else
70
				{
71
					$indexTemplate->coverageAvailable->build(array('coverageValue' => round($coverageValue * 100, 2)));
72
				}
73

                    
74
				$classCoverageTemplates = $indexTemplate->classCoverage;
75

                    
76
				$classCoverageAvailableTemplates = $classCoverageTemplates->classCoverageAvailable;
77
				$classCoverageUnavailableTemplates = $classCoverageTemplates->classCoverageUnavailable;
78

                    
79
				ksort($classes, \SORT_STRING);
80

                    
81
				foreach ($classes as $className => $classFile)
82
				{
83
					$classCoverageTemplates->className = $className;
84
					$classCoverageTemplates->classUrl = str_replace('\\', '/', $className) . self::htmlExtensionFile;
85

                    
86
					$classCoverageValue = $this->coverage->getValueForClass($className);
87

                    
88
					$classCoverageAvailableTemplates->build(array('classCoverageValue' => round($classCoverageValue * 100, 2)));
89

                    
90
					$classCoverageTemplates->build();
91

                    
92
					$classCoverageAvailableTemplates->resetData();
93
					$classCoverageUnavailableTemplates->resetData();
94
				}
95

                    
96
				$this->adapter->file_put_contents($this->destinationDirectory . DIRECTORY_SEPARATOR . 'index.html', (string) $indexTemplate->build());
97

                    
98
				$classTemplate = $this->templateParser->parseFile($this->templatesDirectory . DIRECTORY_SEPARATOR . 'class.tpl');
99

                    
100
				$classTemplate->rootUrl = $this->rootUrl;
101
				$classTemplate->projectName = $this->projectName;
102

                    
103
				$classCoverageAvailableTemplates = $classTemplate->classCoverageAvailable;
104
				$classCoverageUnavailableTemplates = $classTemplate->classCoverageUnavailable;
105

                    
106
				$methodsTemplates = $classTemplate->methods;
107
				$methodTemplates = $methodsTemplates->method;
108

                    
109
				$methodCoverageAvailableTemplates = $methodTemplates->methodCoverageAvailable;
110
				$methodCoverageUnavailableTemplates = $methodTemplates->methodCoverageUnavailable;
111

                    
112
				$sourceFileTemplates = $classTemplate->sourceFile;
113

                    
114
				$lineTemplates = $sourceFileTemplates->line;
115
				$coveredLineTemplates = $sourceFileTemplates->coveredLine;
116
				$notCoveredLineTemplates = $sourceFileTemplates->notCoveredLine;
117

                    
118
				foreach ($this->coverage->getMethods() as $className => $methods)
119
				{
120
					$classTemplate->className = $className;
121

                    
122
					if (substr_count($className, '\\') >= 1)
123
					{
124
						$classTemplate->relativeRootUrl = rtrim(str_repeat('../', substr_count($className, '\\')), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
125
					}
126

                    
127
					$classCoverageValue = $this->coverage->getValueForClass($className);
128

                    
129
					if ($classCoverageValue === null)
130
					{
131
						$classCoverageUnavailableTemplates->build();
132
					}
133
					else
134
					{
135
						$classCoverageAvailableTemplates->build(array('classCoverageValue' => round($classCoverageValue * 100, 2)));
136
					}
137

                    
138
					$reflectedMethods = array();
139

                    
140
					foreach (array_filter($this->getReflectionClass($className)->getMethods(), function($reflectedMethod) use ($className) { return $reflectedMethod->isAbstract() === false && $reflectedMethod->getDeclaringClass()->getName() === $className; }) as $reflectedMethod)
141
					{
142
						$reflectedMethods[$reflectedMethod->getName()] = $reflectedMethod;
143
					}
144

                    
145
					if (sizeof($reflectedMethods) > 0)
146
					{
147
						foreach (array_intersect(array_keys($reflectedMethods), array_keys($methods)) as $methodName)
148
						{
149
							$methodCoverageValue = $this->coverage->getValueForMethod($className, $methodName);
150

                    
151
							if ($methodCoverageValue === null)
152
							{
153
								$methodCoverageUnavailableTemplates->build(array('methodName' => $methodName));
154
							}
155
							else
156
							{
157
								$methodCoverageAvailableTemplates->build(array(
158
										'methodName' => $methodName,
159
										'methodCoverageValue' => round($methodCoverageValue * 100, 2)
160
									)
161
								);
162
							}
163

                    
164
							$methodTemplates->build();
165

                    
166
							$methodCoverageAvailableTemplates->resetData();
167
							$methodCoverageUnavailableTemplates->resetData();
168
						}
169

                    
170
						$methodsTemplates->build();
171

                    
172
						$methodTemplates->resetData();
173
					}
174

                    
175
					$srcFile = $this->adapter->fopen($classes[$className], 'r');
176

                    
177
					if ($srcFile !== false)
178
					{
179
						$methodLines = array();
180

                    
181
						foreach ($reflectedMethods as $reflectedMethodName => $reflectedMethod)
182
						{
183
							$methodLines[$reflectedMethod->getStartLine()] = $reflectedMethodName;
184
						}
185

                    
186
						for ($currentMethod = null, $lineNumber = 1, $line = $this->adapter->fgets($srcFile); $line !== false; $lineNumber++, $line = $this->adapter->fgets($srcFile))
187
						{
188
							if (isset($methodLines[$lineNumber]) === true)
189
							{
190
								$currentMethod = $methodLines[$lineNumber];
191
							}
192

                    
193
							switch (true)
194
							{
195
								case isset($methods[$currentMethod]) === false || (isset($methods[$currentMethod][$lineNumber]) === false || $methods[$currentMethod][$lineNumber] == -2):
196
									$lineTemplateName = 'lineTemplates';
197
									break;
198

                    
199
								case isset($methods[$currentMethod]) === true && isset($methods[$currentMethod][$lineNumber]) === true && $methods[$currentMethod][$lineNumber] == -1:
200
									$lineTemplateName = 'notCoveredLineTemplates';
201
									break;
202

                    
203
								default:
204
									$lineTemplateName = 'coveredLineTemplates';
205
							}
206

                    
207
							${$lineTemplateName}->lineNumber = $lineNumber;
208
							${$lineTemplateName}->code = htmlentities($line, ENT_QUOTES, 'UTF-8');
209

                    
210
							if (isset($methodLines[$lineNumber]) === true)
211
							{
212
								foreach (${$lineTemplateName}->anchor as $anchorTemplate)
213
								{
214
									$anchorTemplate->resetData();
215
									$anchorTemplate->method = $currentMethod;
216
									$anchorTemplate->build();
217
								}
218
							}
219

                    
220
							${$lineTemplateName}
221
								->addToParent()
222
								->resetData()
223
							;
224
						}
225

                    
226
						$this->adapter->fclose($srcFile);
227
					}
228

                    
229
					$file = $this->destinationDirectory . DIRECTORY_SEPARATOR . str_replace('\\', '/', $className) . self::htmlExtensionFile;
230

                    
231
					$directory = $this->adapter->dirname($file);
232

                    
233
					if ($this->adapter->is_dir($directory) === false)
234
					{
235
						$this->adapter->mkdir($directory, 0777, true);
236
					}
237

                    
238
					$this->adapter->file_put_contents($file, (string) $classTemplate->build());
239

                    
240
					$classTemplate->resetData();
241

                    
242
					$classCoverageAvailableTemplates->resetData();
243
					$classCoverageUnavailableTemplates->resetData();
244

                    
245
					$methodsTemplates->resetData();
246

                    
247
					$sourceFileTemplates->resetData();
248
				}
249

                    
250
				$string .= $this->urlPrompt . $this->urlColorizer->colorize(sprintf($this->locale->_('Details of code coverage are available at %s.'), $this->rootUrl)) . PHP_EOL;
251
			}
252
			catch (\exception $exception)
253
			{
254
				$string .= $this->urlPrompt . $this->urlColorizer->colorize(sprintf($this->locale->_('Unable to generate code coverage at %s: %s.'), $this->rootUrl, $exception->getMessage())) . PHP_EOL;
255
			}
256
		}
257

                    
258
		return parent::__toString() . $string;
259
	}
260

                    
261
	public function setReflectionClassInjector(\closure $reflectionClassInjector)100%
262
	{
263
		$closure = new \reflectionMethod($reflectionClassInjector, '__invoke');
264

                    
265
		if ($closure->getNumberOfParameters() != 1)
266
		{
267
			throw new exceptions\logic\invalidArgument('Reflection class injector must take one argument');
268
		}
269

                    
270
		$this->reflectionClassInjector = $reflectionClassInjector;
271

                    
272
		return $this;
273
	}
274

                    
275
	public function getReflectionClass($class)100%
276
	{
277
		if ($this->reflectionClassInjector === null)
278
		{
279
			$reflectionClass = new \reflectionClass($class);
280
		}
281
		else
282
		{
283
			$reflectionClass = $this->reflectionClassInjector->__invoke($class);
284

                    
285
			if ($reflectionClass instanceof \reflectionClass === false)
286
			{
287
				throw new exceptions\runtime\unexpectedValue('Reflection class injector must return a \reflectionClass instance');
288
			}
289
		}
290

                    
291
		return $reflectionClass;
292
	}
293

                    
294
	public function setProjectName($projectName)100%
295
	{
296
		$this->projectName = (string) $projectName;
297

                    
298
		return $this;
299
	}
300

                    
301
	public function getProjectName()
302
	{
303
		return $this->projectName;
304
	}
305

                    
306
	public function setDestinationDirectory($path)100%
307
	{
308
		$this->destinationDirectory = (string) $path;
309

                    
310
		return $this;
311
	}
312

                    
313
	public function getDestinationDirectory()100%
314
	{
315
		return $this->destinationDirectory;
316
	}
317

                    
318
	public function setUrlPrompt(prompt $prompt = null)100%
319
	{
320
		$this->urlPrompt = $prompt ?: new prompt();
321

                    
322
		return $this;
323
	}
324

                    
325
	public function getUrlPrompt()100%
326
	{
327
		return $this->urlPrompt;
328
	}
329

                    
330
	public function setUrlColorizer(colorizer $colorizer = null)100%
331
	{
332
		$this->urlColorizer = $colorizer ?: new colorizer();
333

                    
334
		return $this;
335
	}
336

                    
337
	public function getUrlColorizer()100%
338
	{
339
		return $this->urlColorizer;
340
	}
341

                    
342
	public function setTemplatesDirectory($path = null)100%
343
	{
344
		$this->templatesDirectory = ($path !== null ? (string) $path : atoum\directory . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'coverage');
345

                    
346
		return $this;
347
	}
348

                    
349
	public function getTemplatesDirectory()100%
350
	{
351
		return $this->templatesDirectory;
352
	}
353

                    
354
	public function setTemplateParser(template\parser $parser = null)100%
355
	{
356
		$this->templateParser = $parser ?: new template\parser();
357

                    
358
		return $this;
359
	}
360

                    
361
	public function getTemplateParser()
362
	{
363
		return $this->templateParser;
364
	}
365

                    
366
	public function setRootUrl($rootUrl)100%
367
	{
368
		$this->rootUrl = (string) $rootUrl;
369

                    
370
		return $this;
371
	}
372

                    
373
	public function getRootUrl()100%
374
	{
375
		return $this->rootUrl;
376
	}
377

                    
378
	public function getDestinationDirectoryIterator()100%
379
	{
380
		return new \recursiveIteratorIterator(new \recursiveDirectoryIterator($this->destinationDirectory, \filesystemIterator::KEY_AS_PATHNAME | \filesystemIterator::CURRENT_AS_FILEINFO | \filesystemIterator::SKIP_DOTS), \recursiveIteratorIterator::CHILD_FIRST);
381
	}
382

                    
383
	public function cleanDestinationDirectory()100%
384
	{
385
		try
386
		{
387
			foreach ($this->getDestinationDirectoryIterator() as $inode)
388
			{
389
				if ($inode->isDir() === false)
390
				{
391
					$this->adapter->unlink($inode->getPathname());
392
				}
393
				else if (($pathname = $inode->getPathname()) !== $this->destinationDirectory)
394
				{
395
					$this->adapter->rmdir($pathname);
396
				}
397
			}
398
		}
399
		catch (\exception $exception) {}
400

                    
401
		return $this;
402
	}
403
}