1 : <?php
2 :
3 : /**
4 : * XSLT Benchmarking
5 : * @link https://github.com/masicek/XSLT-Benchmarking
6 : * @author Viktor Mašíček <viktor@masicek.net>
7 : * @license "New" BSD License
8 : */
9 :
10 : namespace XSLTBenchmarking\RunnerConsole;
11 :
12 : require_once LIBS . '/PhpOptions/PhpOptions.min.php';
13 : require_once LIBS . '/PhpPath/PhpPath.min.php';
14 :
15 : require_once ROOT . '/Factory.php';
16 : require_once ROOT . '/Microtime.php';
17 : require_once ROOT . '/Printer.php';
18 :
19 : require_once ROOT . '/TestsGenerator/Generator.php';
20 : require_once ROOT . '/TestsGenerator/Templating/Templating.php';
21 : require_once ROOT . '/TestsGenerator/Params/Params.php';
22 : require_once ROOT . '/TestsGenerator/XmlGenerator/XmlGenerator.php';
23 :
24 : require_once ROOT . '/TestsRunner/Runner.php';
25 : require_once ROOT . '/TestsRunner/Params/Params.php';
26 : require_once ROOT . '/TestsRunner/TestRunner.php';
27 : require_once ROOT . '/TestsRunner/Processors/Processor.php';
28 : require_once ROOT . '/TestsRunner/Processors/MemoryUsage/MemoryUsage.php';
29 : require_once ROOT . '/TestsRunner/Controlor.php';
30 :
31 : require_once ROOT . '/Reports/Printer.php';
32 : require_once ROOT . '/Reports/Merger.php';
33 : require_once ROOT . '/Reports/Convertor/Convertor.php';
34 :
35 : use XSLTBenchmarking\Reports\Convertor;
36 : use XSLTBenchmarking\Microtime;
37 : use XSLTBenchmarking\Printer;
38 : use PhpOptions\Options;
39 : use PhpOptions\Option;
40 : use PhpPath\P;
41 :
42 : /**
43 : * Class for runnig XSLT Benchmark from console
44 : *
45 : * @author Viktor Mašíček <viktor@masicek.net>
46 : */
47 : class Runner
48 : {
49 :
50 :
51 : /**
52 : * Comman line options
53 : *
54 : * @var \PhpOptions\Options
55 : */
56 : private $options;
57 :
58 :
59 : /**
60 : * Factory class for making new objects
61 : *
62 : * @var \XSLTBenchmarking\Factory
63 : */
64 : private $factory;
65 :
66 :
67 : // ---- RUNNING ----
68 :
69 : /**
70 : * Setting the object.
71 : *
72 : * @param string $baseDir Base dir of expected set directories
73 : */
74 : public function __construct($baseDir)
75 : {
76 2 : $this->options = $this->defineOptions($baseDir);
77 2 : $this->factory = new \XSLTBenchmarking\Factory();
78 2 : }
79 :
80 :
81 : /**
82 : * Run XSLT Benchmarking
83 : * - show help
84 : * - generate tests
85 : * - run tests
86 : * - print reports
87 : *
88 : * @return void
89 : */
90 : public function run()
91 : {
92 2 : $options = $this->options;
93 :
94 2 : if ($options->get('Help'))
95 : {// @codeCoverageIgnoreStart
96 : fwrite(STDOUT, $options->getHelp());
97 : return;
98 : }// @codeCoverageIgnoreEnd
99 :
100 2 : if ($options->get('Processors available'))
101 : {// @codeCoverageIgnoreStart
102 : $this->printAvailableProcessors();
103 : return;
104 : }// @codeCoverageIgnoreEnd
105 :
106 : // generating tests
107 2 : if ($options->get('Generate'))
108 2 : {
109 1 : $this->generateTests();
110 1 : }
111 :
112 : // run tests
113 2 : if ($options->get('Run'))
114 2 : {
115 1 : $this->runTests();
116 1 : }
117 :
118 : // merge reports
119 2 : if ($options->get('Merge reports'))
120 2 : {
121 0 : $this->mergeReports();
122 0 : }
123 :
124 : // convert reports
125 2 : if ($options->get('Convert reports'))
126 2 : {
127 0 : $this->convertReports();
128 0 : }
129 2 : }
130 :
131 :
132 : // ---- DEFINE COMMAND-LINE OPTIONS ----
133 :
134 :
135 : /**
136 : * Define excepted command-line options.
137 : *
138 : * @param string $baseDir Base dir of expected set directories
139 : *
140 : * @return \PhpOptions\Options
141 : */
142 : private function defineOptions($baseDir)
143 : {
144 : try {
145 2 : $options = new Options();
146 :
147 : // base settings of options
148 2 : $help = Option::make('Help')->description('Show this help');
149 2 : $options->add($help)->defaults('Help');
150 :
151 2 : $description = 'XSLT Benchmarking ' . VERSION . ' - Console Runner' . PHP_EOL;
152 2 : $description .= 'author: Viktor Masicek <viktor@masicek.net>';
153 2 : $options->description($description);
154 :
155 2 : $optionsList = array();
156 :
157 : // common
158 2 : $optionsList[] = Option::make('Verbose')->description('Print informations during running scrips');
159 :
160 : // directories
161 2 : $templates = $optionsList[] = Option::directory('Templates', $baseDir)
162 2 : ->short()
163 2 : ->value(FALSE)
164 2 : ->defaults('../Data/TestsTemplates')
165 2 : ->description('Directory containing templates for generating tests');
166 2 : $tests = $optionsList[] = Option::directory('Tests', $baseDir, 'makeDir')
167 2 : ->short()
168 2 : ->value(FALSE)
169 2 : ->defaults('../Data/Tests')
170 2 : ->description('Directory for generating tests');
171 2 : $reportsDir = $optionsList[] = Option::directory('Reports', $baseDir, 'makeDir')
172 2 : ->short()
173 2 : ->value(FALSE)
174 2 : ->defaults('../Data/Reports')
175 2 : ->description('Directory for reports of tests');
176 2 : $optionsList[] = Option::directory('Tmp', $baseDir, 'makeDir')
177 2 : ->short()
178 2 : ->value(FALSE)
179 2 : ->defaults('../Tmp')
180 2 : ->description('Temporary directory');
181 :
182 :
183 : // generating tests
184 2 : $optionsList[] = Option::make('Generate')->description('Generating tests from templates');
185 : // @HACK in PhpOption 2.0.0 use array of dirs
186 2 : $optionsList[] = Option::series('Templates dirs', ',')
187 2 : ->short()
188 2 : ->value(FALSE)
189 2 : ->defaults(TRUE)
190 2 : ->description(
191 2 : 'Subdirectories of director set by "' . $templates->getOptions() . '" ' .
192 2 : 'containing tests templates for generating, separated by character ",". ' .
193 2 : 'If this option is not set (or is set without value), ' .
194 2 : 'then all tests templates are selected ' .
195 : '(all subdirectories are considered as tests templates).'
196 2 : );
197 :
198 :
199 : // run tests
200 2 : $optionsList[] = Option::make('Run')->description('Run prepared tests');
201 : // @HACK in PhpOption 2.0.0 use array of dirs
202 2 : $optionsList[] = Option::series('Tests dirs', ',')
203 2 : ->short()
204 2 : ->value(FALSE)
205 2 : ->defaults(TRUE)
206 2 : ->description(
207 2 : 'Subdirectories of director set by "' . $tests->getOptions() . '" ' .
208 2 : 'containing tests for runnig, separated by character ",". ' .
209 2 : 'If this option is not set (or is set without value), ' .
210 2 : 'then all tests are selected ' .
211 : '(all subdirectories are considered as tests).'
212 2 : );
213 :
214 : // @HACK in PhpOption 2.0.0 use array of enum
215 2 : $processors = $optionsList[] = Option::series('Processors', ',')
216 2 : ->value(FALSE)
217 2 : ->defaults(TRUE)
218 2 : ->description(
219 : 'List of tested processors. ' .
220 2 : 'If this option is not set (or is set without value), ' .
221 : 'then all available processors are tested.'
222 2 : );
223 :
224 2 : $processorsExclude = $optionsList[] = Option::series('Processors exclude', ',')
225 2 : ->short('e')
226 2 : ->defaults(array())
227 2 : ->description(
228 : 'List of tested processors, that we want exclude form tested processors.'
229 2 : );
230 :
231 2 : $optionsList[] = Option::make('Processors available')
232 2 : ->short('a')
233 2 : ->description(
234 2 : 'Print list of short names of available processors (possible used in options "' . $processors->getOptions() . '" ' .
235 2 : 'and "' . $processorsExclude->getOptions() . '") and their kernels and full names.'
236 2 : );
237 :
238 2 : $optionsList[] = Option::integer('Repeating', 'unsigned')
239 2 : ->short()
240 2 : ->defaults(1)
241 2 : ->description('Number of repeatig for each test and processor.');
242 :
243 : // merge reports
244 2 : $orderReports = $optionsList[] = Option::enum('Order reports', 'asc,desc,set')
245 2 : ->defaults('set')
246 2 : ->description('Type of ordering for merge reports.');
247 :
248 2 : $optionsList[] = Option::series('Merge reports')
249 2 : ->value(FALSE)
250 2 : ->description(
251 2 : 'List of mergered reports in directory set by "' . $reportsDir->getOptions() . '". ' .
252 2 : 'If this option is set without value, ' .
253 2 : 'then all available reports (without suffix "-merge") are mergered. ' .
254 2 : 'Reports are mergered in set order or ordered by name ' .
255 2 : 'if option "' . $orderReports->getOptions() . '" is set.'
256 2 : );
257 :
258 : // convert reports into set output
259 2 : $convertType = $optionsList[] = Option::enum('Convert type', 'html')
260 2 : ->short()
261 2 : ->defaults('html')
262 2 : ->description('Type of report converting');
263 :
264 : // @HACK
265 : //$optionsList[] = Option::file('Convert reports', $baseDir)
266 2 : $optionsList[] = Option::make('Convert reports')
267 2 : ->value(FALSE)
268 2 : ->description(
269 : 'Convert set report of tests into selected output ' .
270 2 : 'set by "' . $convertType->getOptions() . '". ' .
271 2 : 'If file is not set, latest report in direcotry set by '.
272 2 : '"' . $reportsDir->getOptions() . '" is converted. ' .
273 2 : 'In case of emty dir, report file have to be set. ' .
274 2 : 'Generated converted report are saved into directory set by "' . $reportsDir->getOptions() . '".'
275 2 : );
276 :
277 2 : $options->add($optionsList);
278 :
279 : // dependences + groups
280 2 : $options->dependences('Generate', array(
281 2 : 'Templates',
282 2 : 'Templates dirs',
283 2 : 'Tests',
284 2 : 'Tmp'),
285 : 'Generating tests'
286 2 : );
287 :
288 2 : $options->dependences('Run', array(
289 2 : 'Tests',
290 2 : 'Tests dirs',
291 2 : 'Processors',
292 2 : 'Processors exclude',
293 2 : 'Repeating',
294 2 : 'Reports',
295 2 : 'Tmp')
296 2 : );
297 2 : $options->group('Runnig tests', array(
298 2 : 'Run',
299 2 : 'Tests',
300 2 : 'Tests dirs',
301 2 : 'Processors',
302 2 : 'Processors exclude',
303 2 : 'Processors available',
304 2 : 'Repeating',
305 2 : 'Reports',
306 2 : 'Tmp')
307 2 : );
308 :
309 2 : $options->group('Reporting', array(
310 2 : 'Merge reports',
311 2 : 'Order reports',
312 2 : 'Reports',
313 2 : ));
314 :
315 2 : $options->group('Converting', array(
316 2 : 'Convert reports',
317 2 : 'Convert type',
318 2 : 'Reports',
319 2 : ));
320 :
321 : } catch (\PhpOptions\UserBadCallException $e) {// @codeCoverageIgnoreStart
322 : Printer::info('ERROR: ' . $e->getMessage());
323 : die();
324 : }// @codeCoverageIgnoreEnd
325 :
326 2 : return $options;
327 : }
328 :
329 :
330 : // ---- PARTS OF RUNNING ----
331 :
332 :
333 : /**
334 : * Print available processors
335 : *
336 : * @codeCoverageIgnore
337 : *
338 : * @return void
339 : */
340 : private function printAvailableProcessors()
341 : {
342 : $tmpDir = $this->options->get('Tmp');
343 : $memoryUsage = new \XSLTBenchmarking\TestsRunner\MemoryUsage($tmpDir);
344 : $processor = new \XSLTBenchmarking\TestsRunner\Processor($tmpDir, $memoryUsage);
345 : $processorsDrivers = $processor->getAvailable();
346 :
347 : // get max lengtho of each parts
348 : $maxName = 0;
349 : $maxKernel = 0;
350 : foreach($processorsDrivers as $driverName => $processorDriver)
351 : {
352 : if (strlen($driverName) > $maxName)
353 : {
354 : $maxName = strlen($driverName);
355 : }
356 : if (strlen($processorDriver->getKernel()) > $maxKernel)
357 : {
358 : $maxKernel = strlen($processorDriver->getKernel());
359 : }
360 : }
361 :
362 : // print list
363 : Printer::header('Available processors');
364 : $name = str_pad('SHORT NAME', $maxName, ' ', STR_PAD_LEFT);
365 : $kernel = str_pad('KERNEL', $maxKernel, ' ', STR_PAD_LEFT);
366 : Printer::header($name . ' | ' . $kernel . ' | FULL NAME');
367 : foreach($processorsDrivers as $driverName => $processorDriver)
368 : {
369 : $name = str_pad($driverName, $maxName, ' ', STR_PAD_LEFT);
370 : $kernel = str_pad($processorDriver->getKernel(), $maxKernel, ' ', STR_PAD_LEFT);
371 : Printer::info($name . ' | ' . $kernel . ' | ' . $processorDriver->getFullName());
372 : }
373 : }
374 :
375 :
376 : /**
377 : * Generate tests from tests templates
378 : *
379 : * @return void
380 : */
381 : private function generateTests()
382 : {
383 1 : Printer::header('Generate Tests');
384 :
385 1 : $options = $this->options;
386 1 : $templatesDir = $options->get('Templates');
387 1 : $testsDir = $options->get('Tests');
388 1 : $tmpDir = $options->get('Tmp');
389 :
390 1 : $generator = new \XSLTBenchmarking\TestsGenerator\Generator(
391 1 : $this->factory,
392 1 : new \XSLTBenchmarking\TestsGenerator\Params(
393 1 : new \XSLTBenchmarking\TestsGenerator\XmlGenerator($tmpDir),
394 1 : $tmpDir),
395 1 : new \XSLTBenchmarking\TestsGenerator\Templating($tmpDir),
396 1 : new \XSLTBenchmarking\TestsRunner\Params(),
397 1 : $templatesDir,
398 : $testsDir
399 1 : );
400 :
401 1 : $templatesDirs = $options->get('Templates dirs');
402 :
403 : // generate all templates
404 1 : if ($templatesDirs === TRUE)
405 1 : {
406 1 : $templatesDirs = $this->getSubresources($templatesDir, 'directories');
407 1 : }
408 :
409 1 : if (count($templatesDirs) == 0)
410 1 : {
411 0 : Printer::info('No templateg for generating in "' . $templatesDir . '".');
412 0 : return;
413 : }
414 :
415 1 : foreach ($templatesDirs as $templateDir)
416 : {
417 1 : $generator->addTests($templateDir);
418 1 : }
419 :
420 1 : $start = Microtime::now();
421 1 : $testsNumber = $generator->generateAll($options->get('Verbose'));
422 1 : $end = Microtime::now();
423 1 : $length = Microtime::substract($end, $start);
424 1 : $length = Microtime::humanReadable($length);
425 :
426 1 : Printer::info('Tests generating lasted "' . $length . '". ' . $testsNumber . ' tests were generated from ' . count($templatesDirs) . ' temapltes into directory "' . $testsDir . '".');
427 1 : }
428 :
429 :
430 : /**
431 : * Run all tests
432 : *
433 : * @return void
434 : */
435 : private function runTests()
436 : {
437 1 : Printer::header('Run Tests');
438 :
439 1 : $options = $this->options;
440 1 : $testsDir = $options->get('Tests');
441 1 : $reportsDir = $options->get('Reports');
442 1 : $processors = $options->get('Processors');
443 1 : $processorsExclude = $options->get('Processors exclude');
444 1 : $repeating = $options->get('Repeating');
445 1 : $tmpDir = $options->get('Tmp');
446 :
447 1 : $memoryUsage = new \XSLTBenchmarking\TestsRunner\MemoryUsage($tmpDir);
448 1 : $processor = new \XSLTBenchmarking\TestsRunner\Processor($tmpDir, $memoryUsage);
449 1 : $runner = new \XSLTBenchmarking\TestsRunner\Runner(
450 1 : $this->factory,
451 1 : new \XSLTBenchmarking\TestsRunner\Params(),
452 1 : new \XSLTBenchmarking\TestsRunner\TestRunner(
453 1 : $this->factory,
454 1 : $processor,
455 1 : $processors,
456 1 : $processorsExclude,
457 1 : $repeating,
458 1 : new \XSLTBenchmarking\TestsRunner\Controlor(),
459 : $tmpDir
460 1 : ),
461 1 : new \XSLTBenchmarking\Reports\Printer(
462 1 : $reportsDir,
463 1 : $processor->getInformations()
464 1 : ),
465 : $testsDir
466 1 : );
467 :
468 1 : $testsDirs = $options->get('Tests dirs');
469 :
470 : // generate all templates
471 1 : if ($testsDirs === TRUE)
472 1 : {
473 1 : $testsDirs = $this->getSubresources($testsDir, 'directories');
474 1 : }
475 :
476 1 : if (count($testsDirs) == 0)
477 1 : {
478 0 : Printer::info('No tests for running in "' . $testsDir . '".');
479 0 : return;
480 : }
481 :
482 1 : foreach ($testsDirs as $testDir)
483 : {
484 1 : $runner->addTest($testDir);
485 1 : }
486 :
487 1 : $start = Microtime::now();
488 1 : $reportFilePath = $runner->runAll($options->get('Verbose'));
489 1 : $end = Microtime::now();
490 1 : $length = Microtime::substract($end, $start);
491 1 : $length = Microtime::humanReadable($length);
492 :
493 1 : Printer::info('Tests runnig lasted "' . $length . '". Reports of tests are in "' . $reportFilePath . '".');
494 1 : }
495 :
496 :
497 : /**
498 : * Merge reports into one report file
499 : *
500 : * @return void
501 : */
502 : private function mergeReports()
503 : {
504 0 : Printer::header('Merge reports');
505 :
506 0 : $options = $this->options;
507 0 : $reportsDir = $options->get('Reports');
508 0 : $reportsFiles = $options->get('Merge reports');
509 0 : $orderType = $options->get('Order reports');
510 :
511 : // generate all templates
512 0 : if ($reportsFiles === TRUE || count($reportsFiles) == 0)
513 0 : {
514 0 : $reportsFiles = $this->getSubresources($reportsDir, 'files', '.xml');
515 : // exclude files with suffix '-merge'
516 0 : $reportsFiles = array_filter($reportsFiles, function ($item) {
517 0 : return !preg_match('/-merge.xml$/', $item);
518 0 : });
519 :
520 0 : if (count($reportsFiles) == 0)
521 0 : {
522 0 : Printer::info('No reports for merge in "' . $reportsDir . '".');
523 0 : return;
524 : }
525 0 : }
526 :
527 : // order
528 0 : if ($orderType == 'asc' || $orderType == 'desc')
529 0 : {
530 0 : sort($reportsFiles);
531 0 : }
532 0 : if ($orderType == 'desc')
533 0 : {
534 0 : array_reverse($reportsFiles);
535 0 : }
536 :
537 0 : $merger = new \XSLTBenchmarking\Reports\Merger();
538 :
539 0 : foreach ($reportsFiles as $reportFile)
540 : {
541 0 : $merger->addReportFile(P::m($reportsDir, $reportFile));
542 0 : }
543 :
544 0 : $generatedReport = $merger->merge($reportsDir);
545 :
546 0 : Printer::info(count($reportsFiles) . ' resports were mergered into "' . $generatedReport . '".');
547 0 : }
548 :
549 :
550 : /**
551 : * Convert reports into another format
552 : *
553 : * @return void
554 : */
555 : private function convertReports()
556 : {
557 0 : Printer::header('Convert reports');
558 :
559 0 : $options = $this->options;
560 0 : $reportsDir = $options->get('Reports');
561 0 : $reportFile = $options->get('Convert reports');
562 0 : $convertType = $options->get('Convert type');
563 0 : $tmpDir = $options->get('Tmp');
564 :
565 0 : if ($reportFile === TRUE)
566 0 : {
567 0 : $latestFile = $this->latestFile($reportsDir, 'files', '.xml');
568 0 : if (!$latestFile)
569 0 : {
570 0 : Printer::info('No reports for convert in "' . $reportsDir . '".');
571 0 : return;
572 : }
573 0 : $reportFile = P::m($reportsDir, $latestFile);
574 0 : }
575 :
576 0 : $convertor = new Convertor($tmpDir);
577 0 : $convertor->setDriver($convertType);
578 0 : $generatedFile = $convertor->convert($reportFile, $reportsDir);
579 :
580 0 : Printer::info('"' . $generatedFile . '" was be converted from report file "' . $reportFile . '".');
581 0 : }
582 :
583 :
584 : // ---- HELPS FUNCTIONS ----
585 :
586 :
587 : /**
588 : * Return subdirectories and files names in set path
589 : *
590 : * @param string $path Directory that is scanned
591 : * @param string $type Type of returned resources ('files', 'directories', NULL = all)
592 : * @param string $suffix Required suffix of resources
593 : *
594 : * @return array
595 : */
596 : private function getSubresources($path, $type = NULL, $suffix = '')
597 : {
598 2 : $allResources = scandir($path);
599 :
600 2 : $dirs = array();
601 2 : foreach ($allResources as $resource)
602 : {
603 2 : if (in_array($resource, array('.', '..')))
604 2 : {
605 2 : continue;
606 : }
607 :
608 2 : $fullPath = P::m($path, $resource);
609 :
610 2 : if ($type == 'directories' && !is_dir($fullPath))
611 2 : {
612 0 : continue;
613 : }
614 :
615 2 : if ($type == 'files' && !is_file($fullPath))
616 2 : {
617 0 : continue;
618 : }
619 :
620 2 : if ($suffix && !preg_match('/' . $suffix . '$/', $resource))
621 2 : {
622 0 : continue;
623 : }
624 :
625 2 : $dirs[] = $resource;
626 2 : }
627 :
628 2 : return $dirs;
629 : }
630 :
631 :
632 : /**
633 : * Return lasted file in set directory.
634 : *
635 : * @param string $path Directory that is scanned
636 : * @param string $type Type of returned resources ('files', 'directories', NULL = all)
637 : * @param string $suffix Required suffix of resources
638 : *
639 : * @return string
640 : */
641 : private function latestFile($path, $type = NULL, $suffix = '')
642 : {
643 0 : $lastMod = 0;
644 0 : $lastModFile = '';
645 0 : foreach ($this->getSubresources($path, $type, $suffix) as $resource)
646 : {
647 0 : $fullPath = P::m($path, $resource);
648 0 : if (is_file($fullPath) && filectime($fullPath) > $lastMod)
649 0 : {
650 0 : $lastMod = filectime($fullPath);
651 0 : $lastModFile = $resource;
652 0 : }
653 0 : }
654 :
655 0 : return $lastModFile;
656 : }
657 :
658 :
659 : }
|