├── .gitignore ├── .travis.yml ├── bin └── athletic ├── bootstrap.php ├── box.json.dist ├── composer.json ├── phpunit.xml.dist ├── readme.md ├── src └── Athletic │ ├── Athletic.php │ ├── AthleticEvent.php │ ├── Common │ ├── CmdLine.php │ ├── CmdLineErrorHandler.php │ ├── DICBuilder.php │ ├── ErrorHandlerInterface.php │ └── Exceptions │ │ └── OnlyOneBaselineAllowedException.php │ ├── Discovery │ ├── Parser.php │ └── RecursiveFileLoader.php │ ├── Factories │ ├── AbstractFactory.php │ ├── ClassResultsFactory.php │ ├── ClassRunnerFactory.php │ ├── ErrorExceptionFactory.php │ ├── MethodResultsFactory.php │ └── ParserFactory.php │ ├── Formatters │ ├── DefaultFormatter.php │ ├── FormatterInterface.php │ ├── GroupedFormatter.php │ └── JsonFormatter.php │ ├── Publishers │ ├── PublisherInterface.php │ └── StdOutPublisher.php │ ├── Results │ ├── ClassResults.php │ └── MethodResults.php │ └── Runners │ ├── ClassRunner.php │ └── SuiteRunner.php └── tests ├── Athletic ├── AthleticEventTest.php ├── Common │ ├── CmdLineErrorHandlerTest.php │ └── CmdLineTest.php ├── Discovery │ ├── ParserTest.php │ └── RecursiveFileLoaderTest.php ├── Formatters │ ├── DefaultFormatterTest.php │ ├── GroupedFormatterTest.php │ └── JsonFormatterTest.php ├── Publishers │ └── StdOutPublisherTest.php ├── Results │ └── MethodResultsTest.php ├── Runners │ └── ClassRunnerTest.php └── TestAsset │ └── RunsCounter.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | #phpunit related 2 | phpunit.xml 3 | 4 | #composer related 5 | composer.lock 6 | vendor/ 7 | composer.phar 8 | 9 | #editor related 10 | .idea 11 | 12 | # phar-generation specific 13 | box.json 14 | athletic.phar 15 | 16 | # OS generated files 17 | .DS_Store 18 | .DS_Store? 19 | ._* 20 | .Spotlight-V100 21 | .Trashes 22 | Icon? 23 | ehthumbs.db 24 | Thumbs.db -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.5 4 | - 5.4 5 | - 5.3 6 | - hhvm 7 | 8 | before_script: 9 | - composer install --dev --prefer-source 10 | - mkdir -p build/logs 11 | 12 | script: 13 | - ./vendor/bin/phpunit --coverage-clover ./build/logs/clover.xml 14 | 15 | after_script: 16 | - php vendor/bin/coveralls -v 17 | 18 | matrix: 19 | allow_failures: 20 | - php: hhvm -------------------------------------------------------------------------------- /bin/athletic: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 20 | -------------------------------------------------------------------------------- /bootstrap.php: -------------------------------------------------------------------------------- 1 | run(); -------------------------------------------------------------------------------- /box.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "main": "bootstrap.php", 3 | "output": "athletic.phar", 4 | "compactors": ["Herrera\\Box\\Compactor\\Composer"], 5 | "chmod": "0755", 6 | "directories": ["src/"], 7 | "stub": true 8 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "athletic/athletic", 3 | "description": "PHP Benchmarking Framework", 4 | "keywords": ["benchmark", "benchmarking", "profiling"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Zachary Tong" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.3.9", 14 | "zeptech/annotations": "1.1.*", 15 | "nategood/commando": "0.2.1", 16 | "pimple/pimple": ">=1.0,<3.0" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "~4.0", 20 | "mikey179/vfsStream": "1.2.*", 21 | "mockery/mockery": "0.8.*", 22 | "satooshi/php-coveralls": "0.6.*" 23 | }, 24 | "autoload": { 25 | "psr-0": { 26 | "Athletic": "src/" 27 | } 28 | }, 29 | "bin" : [ "bin/athletic" ] 30 | } 31 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | ./tests/Athletic 16 | 17 | 18 | 19 | ./src 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Athletic 2 | Athletic is a benchmarking framework. It allows developers to benchmark their code without littering microtime() calls everywhere. 3 | 4 | Athletic was inspired by the annotation format that PHPUnit uses. Benchmark tests extend the `AthleticEvent` class and are annotated with specific docblock parameters. The benchmark suite is then run with the Athletic command-line tool. 5 | 6 | | Branch | Unit Tests | Coverage | 7 | | ------ | ---------- | -------- | 8 | | [![Latest Stable Version](https://poser.pugx.org/athletic/athletic/v/stable.png)](https://packagist.org/packages/athletic/athletic) | [![Build Status](https://travis-ci.org/polyfractal/athletic.png?branch=master)](https://travis-ci.org/polyfractal/athletic) | [![Coverage Status](https://coveralls.io/repos/polyfractal/athletic/badge.png?branch=master)](https://coveralls.io/r/polyfractal/athletic?branch=master)| 9 | 10 | # WARNING: No longer maintained 11 | Athletic is not currently being maintained, so it may have bugs or other problems. I would recommend using [PhpBench](https://github.com/phpbench/phpbench), which was inspired by Athletic. It is far more capable, and actively maintained 12 | 13 | ### Why Benchmark? 14 | Because fast code is good! While *premature* optimization is certainly evil, optimization is always an important component of software development. And sometimes you just really need to see if one solution to a problem is faster than an alternative. 15 | 16 | ### Why Use Athletic? 17 | Because it makes benchmarking easy! Athletic is built around annotations. Simply create a benchmarking class and annotate a few methods: 18 | 19 | ```php 20 | /** 21 | * @iterations 1000 22 | */ 23 | public function fastIndexingAlgo() 24 | { 25 | $this->fast->index($this->data); 26 | } 27 | ``` 28 | 29 | Without Athletic, you have to litter your code with microtime() calls and build timing metrics yourself. Or you end up building a benchmark harness remarkably similar to Athletic (but probably with less syntactic sugar...because who builds throw-away code to read test annotations and fancy output?) 30 | 31 | ### Why can't I use xDebug? 32 | xDebug is an excellent profiling tool, but it is not a benchmarking tool. xdebug (and by extension, cachegrind) will show you what is fast/slow inside your method, and is indispensable for actually optimizing your code. But it is not useful for running 1000 iterations of a particular function and determining average execution time. 33 | 34 | ## Quick Installation via Composer 35 | You can easily install Athletic through [Composer](http://getcomposer.org) in two steps: 36 | 37 | ```bash 38 | # Install Composer 39 | curl -sS https://getcomposer.org/installer | php 40 | 41 | # Add Athletic as a dev dependency 42 | php composer.phar require athletic/athletic:~0.1 --dev 43 | ``` 44 | 45 | You can find out more on how to install Composer, configure autoloading, and other best-practices for defining dependencies at [getcomposer.org](http://getcomposer.org). 46 | 47 | # Usage 48 | 49 | To begin using Athletic, you must create an **Event**. This is the analog of a PHPUnit Test Case. An Event will benchmark one or more functions related to your code, compile the results and output them to the command line. 50 | 51 | Here is a sample Event: 52 | 53 | ```php 54 | fast = new Indexing\FastIndexer(); 70 | $this->slow = new Indexing\SlowIndexer(); 71 | $this->data = array('field' => 'value'); 72 | } 73 | 74 | 75 | /** 76 | * @iterations 1000 77 | */ 78 | public function fastIndexingAlgo() 79 | { 80 | $this->fast->index($this->data); 81 | } 82 | 83 | 84 | /** 85 | * @iterations 1000 86 | */ 87 | public function slowIndexingAlgo() 88 | { 89 | $this->slow->index($this->data); 90 | } 91 | 92 | } 93 | ``` 94 | 95 | ### Let's look at how this works. 96 | 97 | ```php 98 | fast = new Indexing\FastIndexer(); 125 | $this->slow = new Indexing\SlowIndexer(); 126 | $this->data = array('field' => 'value'); 127 | } 128 | ``` 129 | Next, we have some private variables and a setUp() method. The `setUp()` method is invoked once at the beginning of each benchmark iteration. This is a good place to instantiate variables that are important to the benchmark itself, populate data, build database connections, etc. In this example, we are building two "Indexing" classes and a sample piece of data. *(More details about setup and tear down are further down in this document)* 130 | 131 | 132 | 133 | ```php 134 | /** 135 | * @iterations 1000 136 | */ 137 | public function fastIndexingAlgo() 138 | { 139 | $this->fast->index($this->data); 140 | } 141 | 142 | 143 | /** 144 | * @iterations 1000 145 | */ 146 | public function slowIndexingAlgo() 147 | { 148 | $this->slow->index($this->data); 149 | } 150 | ``` 151 | Finally, we get to the meat of the benchmark. Here we have two methods that are annotated with `@iterations` in the docblock. The `@iterations` annotation tells Athletic how many times to repeat the method. If a method does not have an iterations annotation, it will not be benchmarked. 152 | 153 | That's it! Now you are ready to run the benchmark. 154 | 155 | ## Running Athletic 156 | A benchmark test is run from the command line: 157 | 158 | ```bash 159 | $ php ./vendor/bin/athletic -p /home/ProjectDir/benchmarks/ -b /home/ProjectDir/vendor/autoload.php 160 | ``` 161 | 162 | The tool has a few flags that can be set: 163 | 164 | | Flag | Long Form | Required | Description | 165 | | ------ | --------- | -------- | ----------- | 166 | | -p | --path | Yes | Specifies the path to the Events to benchmark. Will recursively load all files/classes that extend `AthleticEvent` | 167 | | -b | --bootstrap | | Sets the path to an optional bootstrap file which is included before anything else. This is often used to include an autoloader for your project. | 168 | | -f | --formatter | | User-configured formatter to use instead of DefaultFormatter | 169 | | -h | --help | | Help screen with options and their descriptions | 170 | 171 | 172 | 173 | **Note:** Athletic is intended to be used as a single Phar archive, but that process has not been built yet. Soon! 174 | 175 | ### Output 176 | 177 | So what does the output of a benchmark look like? 178 | 179 | ``` 180 | $ php ./vendor/bin/athletic -p /home/ProjectDir/benchmarks/ -b /home/ProjectDir/vendor/autoload.php 181 | 182 | Vendor\Package\Benchmarks\Indexing\IndexingEvent 183 | Method Name Iterations Average Time Ops/second 184 | --------------------- ------------ -------------- ------------- 185 | fastIndexingAlgo: [1000 ] [0.0020904064178] [478.37588] 186 | slowIndexingAlgo: [1000 ] [0.0048114223480] [177.59184] 187 | ``` 188 | 189 | The default formatter outputs the Event class name, each method name, the number of iterations, average time and operations per second. More advanced formatters will be created in the near future (CSVFormatter, database export, advanced statistics, etc). 190 | 191 | # Further Information 192 | 193 | ### SetUps and TearDowns 194 | 195 | Athletic offers several methods to setup and tear down data/variables. 196 | 197 | | Method | Description | 198 | | ------ | ----------- | 199 | | classSetUp() | Invoked at the beginning of the Event before anything else has occurred | 200 | | setUp() | Invoked once before each iteration of the method being benchmark.| 201 | | classTearDown() | Invoked at the end of the event after everything else has occurred.| 202 | | tearDown() | Invoked after each iteration of a benchmark has completed.| 203 | 204 | There are two levels of setup and tear down to prevent "state leakage" between benchmarks. For example, an object that caches calculations will perform faster on subsequent calls to the method. 205 | 206 | If the goal is to benchmark the initial calculation, it makes sense to place the instantiation of the object in setUp(). 207 | 208 | If the goal, however, is to benchmark the entire process (initial calculation and subsequent caching), then it makes more sense to instantiate the object in classSetUp() so that it is only built once. 209 | 210 | ### Calibration 211 | 212 | Athletic uses Reflection and variable functions to invoke the methods in your Event. Because there is some internal overhead to variable functions, Athletic performs a "calibration" step before each iteration. This step calls an empty calibration method and times how long it takes. This time is then subtracted from the iterations total time, providing a more accurate total time. 213 | -------------------------------------------------------------------------------- /src/Athletic/Athletic.php: -------------------------------------------------------------------------------- 1 | dicBuilder = new DICBuilder($this); 30 | $this->dicBuilder->buildDependencyGraph(); 31 | } 32 | 33 | 34 | public function run() 35 | { 36 | $this->setupErrorHandler(); 37 | $classesToBenchmark = $this->getClassesToBenchmark(); 38 | $this->benchmark($classesToBenchmark); 39 | } 40 | 41 | 42 | /** 43 | * @return string[] 44 | */ 45 | private function getClassesToBenchmark() 46 | { 47 | /** @var RecursiveFileLoader $discovery */ 48 | $discovery = $this['discovery']; 49 | return $discovery->getClasses(); 50 | } 51 | 52 | 53 | /** 54 | * @param string[] $classes 55 | */ 56 | private function benchmark($classes) 57 | { 58 | /** @var SuiteRunner $suite */ 59 | $suite = $this['suiteRunner']; 60 | 61 | $suite->runSuite($classes); 62 | $suite->publishResults(); 63 | 64 | } 65 | 66 | 67 | private function setupErrorHandler() 68 | { 69 | /** @var ErrorHandlerInterface $handler */ 70 | $handler = $this['errorHandler']; 71 | 72 | set_exception_handler(array($handler, 'handleException')); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /src/Athletic/AthleticEvent.php: -------------------------------------------------------------------------------- 1 | methodResultsFactory = $methodResultsFactory; 61 | } 62 | 63 | 64 | /** 65 | * @return MethodResults[] 66 | */ 67 | public function run() 68 | { 69 | $classReflector = new ReflectionClass(get_class($this)); 70 | $classAnnotations = new Annotations($classReflector); 71 | 72 | $methodAnnotations = array(); 73 | foreach ($classReflector->getMethods() as $methodReflector) { 74 | $methodAnnotations[$methodReflector->getName()] = new Annotations($methodReflector); 75 | } 76 | 77 | $this->classSetUp(); 78 | $results = $this->runBenchmarks($methodAnnotations); 79 | $this->classTearDown(); 80 | 81 | return $results; 82 | } 83 | 84 | 85 | /** 86 | * @param Annotations[] $methods 87 | * 88 | * @return MethodResults[] 89 | */ 90 | private function runBenchmarks($methods) 91 | { 92 | $results = array(); 93 | 94 | foreach ($methods as $methodName => $annotations) { 95 | if (isset($annotations['iterations']) === true) { 96 | $results[] = $this->runMethodBenchmark($methodName, $annotations); 97 | } 98 | } 99 | return $results; 100 | } 101 | 102 | 103 | /** 104 | * @param string $method 105 | * @param int $annotations 106 | * 107 | * @return MethodResults 108 | */ 109 | private function runMethodBenchmark($method, $annotations) 110 | { 111 | $iterations = $annotations['iterations']; 112 | $avgCalibration = $this->getCalibrationTime($iterations); 113 | 114 | $results = array(); 115 | for ($i = 0; $i < $iterations; ++$i) { 116 | $this->setUp(); 117 | $results[$i] = $this->timeMethod($method) - $avgCalibration; 118 | $this->tearDown(); 119 | } 120 | 121 | $finalResults = $this->methodResultsFactory->create($method, $results, $iterations); 122 | 123 | $this->setOptionalAnnotations($finalResults, $annotations); 124 | 125 | return $finalResults; 126 | 127 | } 128 | 129 | 130 | /** 131 | * @param string $method 132 | * 133 | * @return mixed 134 | */ 135 | private function timeMethod($method) 136 | { 137 | $start = microtime(true); 138 | $this->$method(); 139 | return microtime(true) - $start; 140 | } 141 | 142 | 143 | /** 144 | * @param int $iterations 145 | * 146 | * @return float 147 | */ 148 | private function getCalibrationTime($iterations) 149 | { 150 | $emptyCalibrationMethod = 'emptyCalibrationMethod'; 151 | $resultsCalibration = array(); 152 | for ($i = 0; $i < $iterations; ++$i) { 153 | $resultsCalibration[$i] = $this->timeMethod($emptyCalibrationMethod); 154 | } 155 | return array_sum($resultsCalibration) / count($resultsCalibration); 156 | } 157 | 158 | 159 | private function emptyCalibrationMethod() 160 | { 161 | 162 | } 163 | 164 | 165 | /** 166 | * @param MethodResults $finalResults 167 | * @param array $annotations 168 | */ 169 | private function setOptionalAnnotations(MethodResults $finalResults, $annotations) 170 | { 171 | if (isset($annotations['group']) === true) { 172 | $finalResults->setGroup($annotations['group']); 173 | } 174 | 175 | if (isset($annotations['baseline']) === true) { 176 | $finalResults->setBaseline(); 177 | } 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /src/Athletic/Common/CmdLine.php: -------------------------------------------------------------------------------- 1 | setCmdArgs($cmdArgs); 27 | 28 | if ($cmdArgs['bootstrap'] !== null) { 29 | require_once($cmdArgs['bootstrap']); 30 | } 31 | 32 | $this->cmdArgs = $cmdArgs; 33 | } 34 | 35 | 36 | /** 37 | * @param Command $cmdArgs 38 | */ 39 | private function setCmdArgs($cmdArgs) 40 | { 41 | $cmdArgs->option('p') 42 | ->require() 43 | ->aka('path') 44 | ->describedAs('Path to benchmark events.'); 45 | 46 | $cmdArgs->flag('b') 47 | ->aka('bootstrap') 48 | ->describedAs('Path to bootstrap file for your project'); 49 | 50 | $cmdArgs->flag('f') 51 | ->aka('formatter') 52 | ->describedAs('User-configured formatter to use instead of DefaultFormatter'); 53 | } 54 | 55 | 56 | /** 57 | * @return mixed 58 | */ 59 | public function getSuitePath() 60 | { 61 | return $this->cmdArgs['path']; 62 | } 63 | 64 | 65 | /** 66 | * @return mixed 67 | */ 68 | public function getFormatter() 69 | { 70 | return $this->cmdArgs['formatter']; 71 | } 72 | } -------------------------------------------------------------------------------- /src/Athletic/Common/CmdLineErrorHandler.php: -------------------------------------------------------------------------------- 1 | command = $command; 28 | $this->errorExceptionFactory = $errorExceptionFactory; 29 | } 30 | 31 | 32 | /* 33 | * {@inheritDoc} 34 | */ 35 | public function handleException(\Exception $exception) 36 | { 37 | $this->command->error($exception); 38 | } 39 | 40 | 41 | /* 42 | * {@inheritDoc} 43 | */ 44 | public function handleError($errorLevel, $errorMessage, $errorFile, $errorLine, $errorContext = array()) 45 | { 46 | // Translate the error to an ErrorException and delegate it to the 47 | // exception handler: 48 | $exception = $this->errorExceptionFactory->create( 49 | $errorLevel, 50 | $errorMessage, 51 | $errorFile, 52 | $errorLine, 53 | $errorContext 54 | ); 55 | 56 | $this->handleException($exception); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Athletic/Common/DICBuilder.php: -------------------------------------------------------------------------------- 1 | athletic = $athletic; 30 | } 31 | 32 | 33 | public function buildDependencyGraph() 34 | { 35 | $this->setupDiscovery(); 36 | $this->setupParser(); 37 | $this->setupCmdLine(); 38 | $this->setupFormatter(); 39 | $this->setupParser(); 40 | $this->setupPublisher(); 41 | $this->setupSuiteRunner(); 42 | $this->setupClassRunner(); 43 | $this->setupMethodResults(); 44 | $this->setupClassResults(); 45 | $this->setupErrorHandler(); 46 | } 47 | 48 | 49 | private function setupClassRunner() 50 | { 51 | $this->athletic['classRunnerClass'] = '\Athletic\Runners\ClassRunner'; 52 | $this->athletic['classRunner'] = function ($dic) { 53 | return function ($class) use ($dic) { 54 | return new $dic['classRunnerClass']($dic['methodResultsFactory'], $class); 55 | }; 56 | }; 57 | 58 | $this->athletic['classRunnerFactoryClass'] = '\Athletic\Factories\ClassRunnerFactory'; 59 | $this->athletic['classRunnerFactory'] = function ($dic) { 60 | return new $dic['classRunnerFactoryClass']($dic); 61 | }; 62 | } 63 | 64 | 65 | private function setupDiscovery() 66 | { 67 | $this->athletic['discoveryClass'] = '\Athletic\Discovery\RecursiveFileLoader'; 68 | $this->athletic['discovery'] = function ($dic) { 69 | /** @var CmdLine $cmdLine */ 70 | $cmdLine = $dic['cmdLine']; 71 | $path = $cmdLine->getSuitePath(); 72 | return new $dic['discoveryClass']($dic['parserFactory'], $path); 73 | }; 74 | } 75 | 76 | 77 | private function setupParser() 78 | { 79 | $this->athletic['parserFactoryClass'] = '\Athletic\Factories\ParserFactory'; 80 | $this->athletic['parserFactory'] = function ($dic) { 81 | return new $dic['parserFactoryClass']($dic); 82 | }; 83 | 84 | $this->athletic['parserClass'] = '\Athletic\Discovery\Parser'; 85 | $this->athletic['parser'] = function ($dic) { 86 | return function ($path) use ($dic) { 87 | return new $dic['parserClass']($path); 88 | }; 89 | }; 90 | } 91 | 92 | 93 | private function setupClassResults() 94 | { 95 | $this->athletic['classResultsFactoryClass'] = '\Athletic\Factories\ClassResultsFactory'; 96 | $this->athletic['classResultsFactory'] = function ($dic) { 97 | return new $dic['classResultsFactoryClass']($dic); 98 | }; 99 | 100 | $this->athletic['classResultsClass'] = '\Athletic\Results\ClassResults'; 101 | $this->athletic['classResults'] = function ($dic) { 102 | return function ($name, $results) use ($dic) { 103 | return new $dic['classResultsClass']($name, $results); 104 | }; 105 | }; 106 | } 107 | 108 | 109 | private function setupMethodResults() 110 | { 111 | $this->athletic['methodResultsFactoryClass'] = '\Athletic\Factories\MethodResultsFactory'; 112 | $this->athletic['methodResultsFactory'] = function ($dic) { 113 | return new $dic['methodResultsFactoryClass']($dic); 114 | }; 115 | 116 | $this->athletic['methodResultsClass'] = '\Athletic\Results\MethodResults'; 117 | $this->athletic['methodResults'] = function ($dic) { 118 | return function ($name, $results, $iterations) use ($dic) { 119 | return new $dic['methodResultsClass']($name, $results, $iterations); 120 | }; 121 | }; 122 | } 123 | 124 | 125 | private function setupCmdLine() 126 | { 127 | $this->athletic['cmdLine'] = function ($dic) { 128 | return new CmdLine($dic['command']); 129 | }; 130 | 131 | $this->athletic['command'] = function ($dic) { 132 | return new Command(); 133 | }; 134 | } 135 | 136 | 137 | private function setupFormatter() 138 | { 139 | /** @var CmdLine $cmdLine */ 140 | $cmdLine = $this->athletic['cmdLine']; 141 | $formatter = $cmdLine->getFormatter(); 142 | 143 | if (isset($formatter) === true) { 144 | if (!class_exists($formatter)) { 145 | // Use a built-in formatter. 146 | $formatter = "\\Athletic\\Formatters\\$formatter"; 147 | } 148 | $this->athletic['formatterClass'] = $formatter; 149 | } else { 150 | $this->athletic['formatterClass'] = '\Athletic\Formatters\DefaultFormatter'; 151 | } 152 | $this->assertValidFormatterClass($this->athletic['formatterClass']); 153 | 154 | $this->athletic['formatter'] = function ($dic) { 155 | return new $dic['formatterClass'](); 156 | }; 157 | } 158 | 159 | 160 | private function setupPublisher() 161 | { 162 | $this->athletic['publisherClass'] = '\Athletic\Publishers\StdOutPublisher'; 163 | $this->athletic['publisher'] = function ($dic) { 164 | return new $dic['publisherClass']($dic['formatter']); 165 | }; 166 | } 167 | 168 | 169 | private function setupSuiteRunner() 170 | { 171 | $this->athletic['suiteRunnerClass'] = '\Athletic\Runners\SuiteRunner'; 172 | $this->athletic['suiteRunner'] = function ($dic) { 173 | return new $dic['suiteRunnerClass']($dic['publisher'], $dic['classResultsFactory'], $dic['classRunnerFactory']); 174 | }; 175 | 176 | } 177 | 178 | 179 | private function setupErrorHandler() 180 | { 181 | 182 | $this->athletic['errorExceptionClass'] = '\ErrorException'; 183 | $this->athletic['errorException'] = function ($dic) { 184 | return function ($errorLevel, $errorMessage, $errorFile, $errorLine, $errorContext) use ($dic) { 185 | return new $dic['errorExceptionClass']( 186 | $errorMessage, 187 | 0, 188 | $errorLevel, 189 | $errorFile, 190 | $errorLine 191 | ); 192 | }; 193 | }; 194 | 195 | $this->athletic['errorExceptionFactoryClass'] = '\Athletic\Factories\ErrorExceptionFactory'; 196 | $this->athletic['errorExceptionFactory'] = function ($dic) { 197 | return new $dic['errorExceptionFactoryClass']($dic); 198 | }; 199 | 200 | $this->athletic['errorHandlerClass'] = '\Athletic\Common\CmdLineErrorHandler'; 201 | $this->athletic['errorHandler'] = function ($dic) { 202 | return new $dic['errorHandlerClass']($dic['command'], $dic['errorExceptionFactory']); 203 | }; 204 | } 205 | 206 | /** 207 | * Asserts that the provided class can be used as output formatter. 208 | * 209 | * @param string $formatterClass 210 | * @throws \InvalidArgumentException If the class doe not meet the requirements (interface and empty constructor) 211 | */ 212 | private function assertValidFormatterClass($formatterClass) 213 | { 214 | $formatterInfo = new \ReflectionClass($formatterClass); 215 | if (!$formatterInfo->implementsInterface('Athletic\Formatters\FormatterInterface')) { 216 | $message = sprintf('%s does not implement the formatter interface.', $formatterClass); 217 | throw new \InvalidArgumentException($message); 218 | } 219 | $constructor = $formatterInfo->getConstructor(); 220 | if ($constructor !== null && $constructor->getNumberOfRequiredParameters() > 0) { 221 | $message = sprintf('Formatter %s must provide a constructor without required parameters.', $formatterClass); 222 | throw new \InvalidArgumentException($message); 223 | } 224 | } 225 | } -------------------------------------------------------------------------------- /src/Athletic/Common/ErrorHandlerInterface.php: -------------------------------------------------------------------------------- 1 | path = $path; 30 | $php = file_get_contents($path); 31 | $this->parsePHP($php); 32 | } 33 | 34 | 35 | /** 36 | * @return bool 37 | */ 38 | public function isAthleticEvent() 39 | { 40 | if (strpos($this->parentClassName, 'AthleticEvent') !== false) { 41 | return true; 42 | } else { 43 | return false; 44 | } 45 | } 46 | 47 | 48 | /** 49 | * @return string 50 | */ 51 | public function getFQN() 52 | { 53 | $fqn = ''; 54 | 55 | if (isset($this->namespace)) { 56 | $fqn .= $this->namespace; 57 | } 58 | 59 | if (isset($this->className) === true) { 60 | $fqn .= '\\' . $this->className; 61 | } 62 | 63 | return $fqn; 64 | } 65 | 66 | 67 | /** 68 | * @return string 69 | */ 70 | public function getPath() 71 | { 72 | return $this->path; 73 | } 74 | 75 | 76 | private function parsePHP($php) 77 | { 78 | $tokens = token_get_all($php); 79 | 80 | $this->setNamespace($tokens); 81 | $this->setClassName($tokens); 82 | $this->setParentClass($tokens); 83 | } 84 | 85 | 86 | /** 87 | * @param array $tokens 88 | */ 89 | private function setNamespace($tokens) 90 | { 91 | $tokenCount = count($tokens); 92 | 93 | for ($i = 0; $i < $tokenCount; ++$i) { 94 | if ($tokens[$i][0] === T_CLASS) { 95 | return; 96 | } 97 | 98 | if ($tokens[$i][0] === T_NAMESPACE) { 99 | $this->namespace = $this->extractNamespacePath($tokens, $i); 100 | return; 101 | } 102 | } 103 | return; 104 | } 105 | 106 | 107 | /** 108 | * @param array $tokens 109 | * @param int $i 110 | * 111 | * @return string 112 | */ 113 | private function extractNamespacePath($tokens, $i) 114 | { 115 | $namespace = ''; 116 | $i += 2; 117 | 118 | while ($tokens[$i][0] === T_NS_SEPARATOR || $tokens[$i][0] === T_STRING) { 119 | $namespace .= $tokens[$i][1]; 120 | $i += 1; 121 | } 122 | 123 | return $namespace; 124 | } 125 | 126 | 127 | /** 128 | * @param array $tokens 129 | */ 130 | private function setClassName($tokens) 131 | { 132 | $tokenCount = count($tokens); 133 | 134 | for ($i = 0; $i < $tokenCount; ++$i) { 135 | if (is_array($tokens[$i]) === true && $tokens[$i][0] === T_CLASS) { 136 | $this->className = $tokens[$i + 2][1]; 137 | return; 138 | } 139 | } 140 | } 141 | 142 | 143 | /** 144 | * @param array $tokens 145 | */ 146 | private function setParentClass($tokens) 147 | { 148 | $tokenCount = count($tokens); 149 | 150 | for ($i = 0; $i < $tokenCount; ++$i) { 151 | if ($tokens[$i][0] === T_EXTENDS) { 152 | $this->parentClassName = $this->extractParentClassName($tokens, $i); 153 | return; 154 | } 155 | } 156 | return; 157 | } 158 | 159 | 160 | /** 161 | * @param array $tokens 162 | * @param int $i 163 | * 164 | * @return string 165 | */ 166 | private function extractParentClassName($tokens, $i) 167 | { 168 | $parentClass = ''; 169 | $i += 2; 170 | 171 | while ($tokens[$i][0] !== T_WHITESPACE) { 172 | $parentClass .= $tokens[$i][1]; 173 | $i += 1; 174 | } 175 | return $parentClass; 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/Athletic/Discovery/RecursiveFileLoader.php: -------------------------------------------------------------------------------- 1 | parserFactory = $parserFactory; 32 | $files = (is_file($path)) ? array($path) : $this->scanDirectory($path); 33 | $parsedPHPFiles = $this->parsePHPFiles($files); 34 | $athleticClasses = $this->getAthleticClasses($parsedPHPFiles); 35 | 36 | $this->fqns = $this->getFQN($athleticClasses); 37 | 38 | } 39 | 40 | 41 | /** 42 | * @return string[] 43 | */ 44 | public function getClasses() 45 | { 46 | return $this->fqns; 47 | } 48 | 49 | 50 | /** 51 | * @param array $files 52 | * 53 | * @return array 54 | */ 55 | private function parsePHPFiles($files) 56 | { 57 | $parsedPHP = array(); 58 | foreach ($files as $file) { 59 | $parsedPHP[] = $this->parserFactory->create($file); 60 | } 61 | 62 | return $parsedPHP; 63 | } 64 | 65 | 66 | /** 67 | * @param Parser[] $parsedPHPFiles 68 | * 69 | * @return Parser[] 70 | */ 71 | private function getAthleticClasses($parsedPHPFiles) 72 | { 73 | /** @var Parser[] $athleticClasses */ 74 | $athleticClasses = array(); 75 | 76 | foreach ($parsedPHPFiles as $class) { 77 | /** @var Parser $class */ 78 | if ($class->isAthleticEvent() === true) { 79 | $athleticClasses[] = $class; 80 | } 81 | } 82 | 83 | return $athleticClasses; 84 | } 85 | 86 | 87 | /** 88 | * @param Parser[] $athleticClasses 89 | * 90 | * @return array 91 | */ 92 | private function getFQN($athleticClasses) 93 | { 94 | $fqns = array(); 95 | foreach ($athleticClasses as $class) { 96 | $fqns[] = $class->getFQN(); 97 | } 98 | 99 | return $fqns; 100 | } 101 | 102 | 103 | /** 104 | * @param string $dir 105 | * @param string $prefix 106 | * 107 | * @return array 108 | */ 109 | private function scanDirectory($dir, $prefix = '') 110 | { 111 | $dir = rtrim($dir, '\\/'); 112 | $result = array(); 113 | 114 | foreach (scandir($dir) as $f) { 115 | if ($f !== '.' and $f !== '..') { 116 | if (is_dir("$dir/$f")) { 117 | $result = array_merge($result, $this->scanDirectory("$dir/$f", "$prefix$f/")); 118 | } else { 119 | $result[] = $dir . '/' . $f; 120 | } 121 | } 122 | } 123 | 124 | return $result; 125 | } 126 | } -------------------------------------------------------------------------------- /src/Athletic/Factories/AbstractFactory.php: -------------------------------------------------------------------------------- 1 | container = $container; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/Athletic/Factories/ClassResultsFactory.php: -------------------------------------------------------------------------------- 1 | container['classResults']($name, $results); 21 | } 22 | } -------------------------------------------------------------------------------- /src/Athletic/Factories/ClassRunnerFactory.php: -------------------------------------------------------------------------------- 1 | container['classRunner']($class); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Athletic/Factories/ErrorExceptionFactory.php: -------------------------------------------------------------------------------- 1 | container['errorException']($errorLevel, $errorMessage, $errorFile, $errorLine, $errorContext); 31 | } 32 | } -------------------------------------------------------------------------------- /src/Athletic/Factories/MethodResultsFactory.php: -------------------------------------------------------------------------------- 1 | container['methodResults']($name, $results, $iterations); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Athletic/Factories/ParserFactory.php: -------------------------------------------------------------------------------- 1 | container['parser']($path); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Athletic/Formatters/DefaultFormatter.php: -------------------------------------------------------------------------------- 1 | getClassName() . "\n"; 38 | 39 | // build a table containing the formatted numbers 40 | $table = array(); 41 | foreach ($result as $methodResult) { 42 | $table[] = array( 43 | $methodResult->methodName, 44 | number_format($methodResult->iterations), 45 | number_format($methodResult->avg, 13), 46 | number_format($methodResult->ops, 5), 47 | ); 48 | } 49 | 50 | // determine column widths for table layout 51 | $lengths = array_map('strlen', $header); 52 | foreach ($table as $row) { 53 | foreach ($row as $name => $value) { 54 | $lengths[$name] = max(strlen($value), $lengths[$name]); 55 | } 56 | } 57 | 58 | // format header and table rows 59 | $returnString .= sprintf( 60 | " %s %s %s %s\n", 61 | str_pad($header[0], $lengths[0]), 62 | str_pad($header[1], $lengths[1]), 63 | str_pad($header[2], $lengths[2]), 64 | str_pad($header[3], $lengths[3]) 65 | ); 66 | $returnString .= sprintf( 67 | " %s %s %s %s\n", 68 | str_repeat('-', $lengths[0] + 1), 69 | str_repeat('-', $lengths[1] + 2), 70 | str_repeat('-', $lengths[2] + 2), 71 | str_repeat('-', $lengths[3] + 2) 72 | ); 73 | foreach ($table as $row) { 74 | $returnString .= sprintf( 75 | " %s: [%s] [%s] [%s]\n", 76 | str_pad($row[0], $lengths[0]), 77 | str_pad($row[1], $lengths[1], ' ', STR_PAD_LEFT), 78 | str_pad($row[2], $lengths[2]), 79 | str_pad($row[3], $lengths[3]) 80 | ); 81 | } 82 | 83 | $returnString .= "\n\n"; 84 | } 85 | 86 | return $returnString; 87 | } 88 | } -------------------------------------------------------------------------------- /src/Athletic/Formatters/FormatterInterface.php: -------------------------------------------------------------------------------- 1 | transformResultsToArray($results); 30 | $returnString = $this->getFormattedString($returnData); 31 | 32 | return $returnString; 33 | } 34 | 35 | 36 | /** 37 | * @param ClassResults[] $results 38 | * 39 | * @return array 40 | */ 41 | private function transformResultsToArray($results) 42 | { 43 | $returnData = array(); 44 | 45 | foreach ($results as $result) { 46 | 47 | $class = $result->getClassName(); 48 | $returnData[$class] = array(); 49 | 50 | foreach ($result as $methodResult) { 51 | if (isset($methodResult->group) === true) { 52 | $group = $methodResult->group; 53 | } else { 54 | $group = "No Group"; 55 | } 56 | 57 | $baseline = $methodResult->baseline; 58 | 59 | if ($baseline === true) { 60 | $baseline = "baseline"; 61 | } else { 62 | $baseline = "experimental"; 63 | } 64 | $returnData[$class][$group][$baseline][] = $methodResult; 65 | 66 | } 67 | } 68 | 69 | return $returnData; 70 | } 71 | 72 | 73 | /** 74 | * @param array $data 75 | * 76 | * @return string 77 | */ 78 | private function getFormattedString($data) 79 | { 80 | $returnString = ""; 81 | 82 | foreach ($data as $className => $groups) { 83 | $returnString .= "$className\n"; 84 | 85 | foreach ($groups as $groupName => $results) { 86 | 87 | $returnString .= $this->getGroupHeaderString($groupName); 88 | $returnString .= $this->getBaselineString($results); 89 | 90 | $baselineTime = $this->getBaselineTime($results); 91 | 92 | if (isset($results['experimental']) === true) { 93 | foreach ($results['experimental'] as $result) { 94 | $returnString .= $this->parseMethodResultToString($result, false, $baselineTime); 95 | 96 | } 97 | } 98 | $returnString .= "\n"; 99 | } 100 | $returnString .= "\n"; 101 | } 102 | 103 | 104 | return $returnString; 105 | } 106 | 107 | 108 | /** 109 | * @param string $groupName 110 | * @param int $padding 111 | * 112 | * @return string 113 | */ 114 | private function getGroupHeaderString($groupName, $padding = 30) 115 | { 116 | $returnString = " $groupName\n " . str_pad( 117 | 'Method Name', 118 | $padding 119 | ) . " Iterations Average Time Ops/s Relative\n"; 120 | 121 | $returnString .= ' ' . str_repeat('-', $padding) . " ---------- ------------ -------------- --------- ---------\n"; 122 | 123 | return $returnString; 124 | } 125 | 126 | 127 | /** 128 | * @param array $results 129 | * 130 | * @return string 131 | * @throws \Athletic\Common\Exceptions\OnlyOneBaselineAllowedException 132 | */ 133 | private function getBaselineString($results) 134 | { 135 | if (isset($results['baseline']) !== true) { 136 | return ""; 137 | } 138 | 139 | if (count($results['baseline']) > 1) { 140 | throw new OnlyOneBaselineAllowedException("Only one baseline may be specified per group."); 141 | } 142 | 143 | return $this->parseMethodResultToString($results['baseline'][0], true); 144 | } 145 | 146 | 147 | /** 148 | * @param array $results 149 | * 150 | * @return int 151 | */ 152 | private function getBaselineTime($results) 153 | { 154 | if (isset($results['baseline'][0]) === true) { 155 | return $results['baseline'][0]->avg; 156 | } else { 157 | return 0; 158 | } 159 | } 160 | 161 | /** 162 | * @param MethodResults $result 163 | * @param null|float $baselineTime 164 | * @param bool $baseline 165 | * 166 | * @return string 167 | */ 168 | private function parseMethodResultToString(MethodResults $result, $baseline, $baselineTime = null) 169 | { 170 | $method = str_pad($result->methodName, 30); 171 | $iterations = str_pad(number_format($result->iterations), 10); 172 | $avg = str_pad(number_format($result->avg, 13), 13); 173 | $ops = str_pad(number_format($result->ops, 5), 7); 174 | 175 | if ($baseline === true) { 176 | return " $method: [Baseline] [$iterations] [$avg] [$ops]\n"; 177 | } else { 178 | 179 | if ($result->avg === $baselineTime) { 180 | $percentage = "[100%]"; 181 | } elseif ($baselineTime === 0) { 182 | $percentage = ""; 183 | } else { 184 | $percentage = str_pad(number_format(($result->avg / $baselineTime)*100, 2), 2); 185 | $percentage = "[$percentage%]"; 186 | } 187 | 188 | return " $method: [$iterations] [$avg] [$ops] $percentage\n"; 189 | } 190 | 191 | } 192 | } -------------------------------------------------------------------------------- /src/Athletic/Formatters/JsonFormatter.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class JsonFormatter implements FormatterInterface 16 | { 17 | /** 18 | * List of stats MethodResults has. 19 | * 20 | * @type string[] 21 | * @since 0.1.0 22 | */ 23 | protected $statKeys = array( 24 | 'iterations', 25 | 'sum', 26 | 'min', 27 | 'max', 28 | 'avg', 29 | 'ops', 30 | 'group', 31 | ); 32 | /** 33 | * This constant may be used to force formatter to show all results in a 34 | * plain list under tested class name key. This is intended to be default 35 | * behavior. 36 | * 37 | * @type int 38 | * @since 0.1.0 39 | */ 40 | const STRATEGY_LIST_ALL = 1; 41 | /** 42 | * This constant may be used to force formatter to show only results for 43 | * methods that belong to group. 44 | * 45 | * @type int 46 | * @since 0.1.0 47 | */ 48 | const STRATEGY_SHOW_GROUPED = 2; 49 | /** 50 | * This constant may be used to force formatter to show only results that 51 | * don't belong to any group. 52 | * 53 | * @type int 54 | * @since 0.1.0 55 | */ 56 | const STRATEGY_SHOW_NONGROUPED = 3; 57 | /** 58 | * This constant may be used to force formatter to show both grouped and 59 | * non-grouped results under `groups` and `methods` keys correspondingly. 60 | * 61 | * @type int 62 | * @since 0.1.0 63 | */ 64 | const STRATEGY_MIX_VIEWS = 4; 65 | /** 66 | * Prints results in JSON format. 67 | * 68 | * @param ClassResults[] $results List of testing results. 69 | * @param int $displayStrategy Which strategy to use to display 70 | * results (defaults to listing all 71 | * results). Use `self::STRATEGY_*` 72 | * constants to set it. 73 | * @param bool $prettyPrint Whether to return data in 74 | * human-readable format or just as 75 | * single string. 76 | * 77 | * @return string JSON-encoded data. 78 | * @since 0.1.0 79 | */ 80 | public function getFormattedResults( 81 | $results, 82 | $displayStrategy = self::STRATEGY_LIST_ALL, 83 | $prettyPrint = false 84 | ) { 85 | $data = $this->sortResults($results, $displayStrategy); 86 | $options = JSON_FORCE_OBJECT; 87 | if ($prettyPrint) { 88 | $options |= JSON_PRETTY_PRINT; 89 | } 90 | return json_encode($data, $options) . PHP_EOL; 91 | } 92 | 93 | /** 94 | * Reformats results from original Athletic result data to encode-ready 95 | * nested array. 96 | * 97 | * @param ClassResults[] $results Athletic internal output. 98 | * @param int $displayStrategy Strategy to force particular 99 | * kind of output (groups / 100 | * nongrouped methods / both / both 101 | * as plain list). Use 102 | * `self::STRATEGY_*` constants to 103 | * set this value. 104 | * 105 | * @return array List of Athletic results as a nested array, filtered 106 | * according to provided strategy. 107 | * @since 0.1.0 108 | */ 109 | protected function sortResults( 110 | array $results, 111 | $displayStrategy = self::STRATEGY_LIST_ALL 112 | ) { 113 | $classes = array(); 114 | foreach ($results as $classResult) { 115 | $className = $classResult->getClassName(); 116 | $results = $this->getClassResults($classResult); 117 | $results = $this->filterClassResults($results, $displayStrategy); 118 | $classes[$className] = $results; 119 | } 120 | return $classes; 121 | } 122 | 123 | /** 124 | * Formats class results as a single array of method results (formatted as 125 | * arrays too). 126 | * 127 | * @param ClassResults $classResults Class results. 128 | * 129 | * @return array Set of method results in [method => [results]] format. 130 | * @since 0.1.0 131 | */ 132 | protected function getClassResults(ClassResults $classResults) { 133 | $stats = array(); 134 | /** @type MethodResults $result */ 135 | foreach ($classResults as $result) { 136 | $data = array(); 137 | foreach ($this->statKeys as $key) { 138 | $data[$key] = $result->$key; 139 | } 140 | $stats[$result->methodName] = $data; 141 | } 142 | return $stats; 143 | } 144 | 145 | /** 146 | * Filters or recombines class results according to provided strategy. 147 | * 148 | * @param array $results Array of raw class results. 149 | * @param int $strategy One of `self::STRATEGY_*` constant values. 150 | * 151 | * @return array List of class results. 152 | * @since 0.1.0 153 | */ 154 | protected function filterClassResults( 155 | array $results, 156 | $strategy 157 | ) { 158 | if ($strategy === self::STRATEGY_LIST_ALL) { 159 | return $results; 160 | } 161 | $groups = array(); 162 | $methods = array(); 163 | foreach ($results as $method => $result) { 164 | $groupName = $result['group']; 165 | unset($result['group']); 166 | if ($groupName) { 167 | if (!isset($groups[$groupName])) { 168 | $groups[$groupName] = array(); 169 | } 170 | $groups[$groupName][$method] = $result; 171 | } else { 172 | $methods[$method] = $result; 173 | } 174 | } 175 | switch ($strategy) { 176 | case self::STRATEGY_SHOW_GROUPED: 177 | return $groups; 178 | case self::STRATEGY_SHOW_NONGROUPED: 179 | return $methods; 180 | case self::STRATEGY_MIX_VIEWS: 181 | return array('methods' => $methods, 'groups' => $groups); 182 | default: 183 | throw new \InvalidArgumentException( 184 | 'Unknown results display strategy' 185 | ); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/Athletic/Publishers/PublisherInterface.php: -------------------------------------------------------------------------------- 1 | formatter = $formatter; 29 | } 30 | 31 | 32 | /** 33 | * @param ClassResults[] $results 34 | * 35 | * @return void 36 | */ 37 | public function publish($results) 38 | { 39 | $data = $this->formatter->getFormattedResults($results); 40 | echo $data; 41 | } 42 | } -------------------------------------------------------------------------------- /src/Athletic/Results/ClassResults.php: -------------------------------------------------------------------------------- 1 | className = $className; 34 | $this->results = $results; 35 | } 36 | 37 | 38 | /** 39 | * @return ArrayIterator|\Traversable 40 | */ 41 | public function getIterator() 42 | { 43 | return new ArrayIterator($this->results); 44 | } 45 | 46 | 47 | /** 48 | * @return string 49 | */ 50 | public function getClassName() 51 | { 52 | return $this->className; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/Athletic/Results/MethodResults.php: -------------------------------------------------------------------------------- 1 | methodName = $name; 31 | $this->results = $results; 32 | $this->iterations = $iterations; 33 | $this->sum = array_sum($results); 34 | $this->avg = ($this->sum / count($results)); 35 | $this->max = max($results); 36 | $this->min = min($results); 37 | $this->ops = ($this->sum == 0.0) ? NAN : ($iterations / $this->sum); 38 | $this->baseline = false; 39 | } 40 | 41 | 42 | /** 43 | * @param string $group 44 | */ 45 | public function setGroup($group) 46 | { 47 | $this->group = $group; 48 | } 49 | 50 | public function setBaseline() 51 | { 52 | $this->baseline = true; 53 | } 54 | } -------------------------------------------------------------------------------- /src/Athletic/Runners/ClassRunner.php: -------------------------------------------------------------------------------- 1 | class = $class; 39 | $this->methodResultsFactory = $methodResultsFactory; 40 | } 41 | 42 | 43 | /** 44 | * @return MethodResults[] 45 | */ 46 | public function run() 47 | { 48 | if ($this->isBenchmarkableClass() !== true) { 49 | return array(); 50 | } 51 | 52 | $class = $this->class; 53 | 54 | /** @var AthleticEvent $object */ 55 | $object = new $class(); 56 | 57 | $object->setMethodFactory($this->methodResultsFactory); 58 | return $object->run(); 59 | } 60 | 61 | 62 | /** 63 | * @return bool 64 | */ 65 | private function isBenchmarkableClass() 66 | { 67 | $reflectionClass = new ReflectionClass($this->class); 68 | return ($reflectionClass->isAbstract() !== true && $reflectionClass->isSubclassOf( 69 | '\Athletic\AthleticEvent' 70 | ) === true); 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/Athletic/Runners/SuiteRunner.php: -------------------------------------------------------------------------------- 1 | publisher = $publisher; 46 | $this->classRunnerFactory = $classRunnerFactory; 47 | $this->classResultsFactory = $classResultsFactory; 48 | } 49 | 50 | 51 | /** 52 | * @param string[] $classesToRun 53 | */ 54 | public function runSuite($classesToRun) 55 | { 56 | $results = array(); 57 | 58 | foreach ($classesToRun as $class) { 59 | $results[] = $this->runClass($class); 60 | } 61 | 62 | $this->results = $results; 63 | } 64 | 65 | 66 | /** 67 | * @param string $class 68 | * 69 | * @return ClassResults 70 | */ 71 | public function runClass($class) 72 | { 73 | $classRunner = $this->classRunnerFactory->create($class); 74 | $methodResults = $classRunner->run(); 75 | 76 | return $this->classResultsFactory->create($class, $methodResults); 77 | 78 | } 79 | 80 | 81 | /** 82 | * @return array 83 | */ 84 | public function publishResults() 85 | { 86 | $this->publisher->publish($this->results); 87 | } 88 | } -------------------------------------------------------------------------------- /tests/Athletic/AthleticEventTest.php: -------------------------------------------------------------------------------- 1 | 18 | * 19 | * @covers \Athletic\AthleticEvent 20 | */ 21 | class AthleticEventTest extends PHPUnit_Framework_TestCase 22 | { 23 | /** 24 | * @var \Athletic\Factories\MethodResultsFactory|\PHPUnit_Framework_MockObject_MockObject 25 | */ 26 | private $resultsFactory; 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | public function setUp() 32 | { 33 | $this->resultsFactory = $this->getMock('Athletic\Factories\MethodResultsFactory', array(), array(), '', false); 34 | 35 | $this 36 | ->resultsFactory 37 | ->expects($this->any()) 38 | ->method('create') 39 | ->will($this->returnCallback(function ($name, $result, $iterations) { 40 | return new MethodResults($name, $result, $iterations); 41 | })); 42 | } 43 | 44 | public function testCorrectRunsCount() 45 | { 46 | $event = new RunsCounter(); 47 | 48 | $event->setMethodFactory($this->resultsFactory); 49 | 50 | $results = $event->run(); 51 | 52 | $this->assertCount(1, $results); 53 | 54 | /* @var $result MethodResults */ 55 | $result = reset($results); 56 | 57 | $this->assertInstanceOf('Athletic\Results\MethodResults', $result); 58 | 59 | $this->assertCount(5, $result->results); 60 | $this->assertSame(5, $result->iterations); 61 | $this->assertSame(5, $event->runs); 62 | $this->assertSame(5, $event->setUps); 63 | $this->assertSame(5, $event->tearDowns); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Athletic/Common/CmdLineErrorHandlerTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('error') 30 | ->with($mockException) 31 | ->getMock(); 32 | 33 | $mockErrorExceptionFactory = m::mock('\Athletic\Factories\ErrorExceptionFactory'); 34 | 35 | $handler = new CmdLineErrorHandler($mockCommand, $mockErrorExceptionFactory); 36 | $handler->handleException($mockException); 37 | 38 | } 39 | 40 | 41 | public function testError() 42 | { 43 | $errorLevel = 1; 44 | $errorMessage = 'abc'; 45 | $errorFile = 'abc'; 46 | $errorLine = 1; 47 | 48 | $mockException = m::mock('\Exception'); 49 | $mockCommand = m::mock('\Commando\Command') 50 | ->shouldReceive('error') 51 | ->with($mockException) 52 | ->getMock(); 53 | 54 | $mockErrorExceptionFactory = m::mock('\Athletic\Factories\ErrorExceptionFactory') 55 | ->shouldReceive('create') 56 | ->with($errorLevel, $errorMessage, $errorFile, $errorLine, array()) 57 | ->andReturn($mockException) 58 | ->getMock(); 59 | 60 | $handler = new CmdLineErrorHandler($mockCommand, $mockErrorExceptionFactory); 61 | 62 | $handler->handleError($errorLevel, $errorMessage, $errorFile, $errorLine); 63 | } 64 | 65 | 66 | } -------------------------------------------------------------------------------- /tests/Athletic/Common/CmdLineTest.php: -------------------------------------------------------------------------------- 1 | root = vfsStream::setup('root'); 26 | } 27 | 28 | 29 | public function tearDown() 30 | { 31 | m::close(); 32 | } 33 | 34 | 35 | public function testConstructor() 36 | { 37 | $mockCommand = m::mock('\Commando\Command'); 38 | $mockCommand->shouldReceive('option->require->aka->describedAs')->once(); 39 | $mockCommand->shouldReceive('flag->aka->describedAs')->twice(); 40 | $mockCommand->shouldReceive('offsetGet')->once()->with('bootstrap')->andReturnNull(); 41 | 42 | $cmdLine = new CmdLine($mockCommand); 43 | } 44 | 45 | 46 | public function testConstructorWithBootstrap() 47 | { 48 | $bootstrap = ' array( 52 | 'bootstrap.php' => $bootstrap 53 | ) 54 | ); 55 | 56 | vfsStream::create($structure, $this->root); 57 | $path = vfsStream::url('root\bootstrap\bootstrap.php'); 58 | 59 | $mockCommand = m::mock('\Commando\Command'); 60 | $mockCommand->shouldReceive('option->require->aka->describedAs')->once(); 61 | $mockCommand->shouldReceive('flag->aka->describedAs')->twice(); 62 | $mockCommand->shouldReceive('offsetGet')->twice()->with('bootstrap')->andReturn($path); 63 | 64 | $cmdLine = new CmdLine($mockCommand); 65 | 66 | $loadedClasses = get_declared_classes(); 67 | 68 | $this->assertContains('Vendor\Package\Child1\Class1', $loadedClasses); 69 | } 70 | } -------------------------------------------------------------------------------- /tests/Athletic/Discovery/ParserTest.php: -------------------------------------------------------------------------------- 1 | root = vfsStream::setup('root'); 22 | 23 | $structure = array( 24 | 'testAthleticWithNamespace.php' => ' ' ' ' ' ' ' ' 'root); 36 | } 37 | 38 | 39 | public function tearDown() 40 | { 41 | m::close(); 42 | } 43 | 44 | 45 | public function testAthleticWithNamespace() 46 | { 47 | $path = vfsStream::url('root\testAthleticWithNamespace.php'); 48 | $parser = new Parser($path); 49 | $this->assertTrue($parser->isAthleticEvent()); 50 | $this->assertEquals('Vendor\Package\Child1\Class1', $parser->getFQN()); 51 | $this->assertEquals('vfs://root/testAthleticWithNamespace.php', $parser->getPath()); 52 | } 53 | 54 | 55 | public function testNonAthleticWithNamespace() 56 | { 57 | $path = vfsStream::url('root\testNonAthleticWithNamespace.php'); 58 | $parser = new Parser($path); 59 | $this->assertFalse($parser->isAthleticEvent()); 60 | $this->assertEquals('Vendor\Package\Child1\Class1', $parser->getFQN()); 61 | $this->assertEquals('vfs://root/testNonAthleticWithNamespace.php', $parser->getPath()); 62 | } 63 | 64 | 65 | public function testAthleticNoNamespace() 66 | { 67 | $path = vfsStream::url('root\testAthleticNoNamespace.php'); 68 | $parser = new Parser($path); 69 | $this->assertTrue($parser->isAthleticEvent()); 70 | $this->assertEquals('\Class1', $parser->getFQN()); 71 | $this->assertEquals('vfs://root/testAthleticNoNamespace.php', $parser->getPath()); 72 | } 73 | 74 | 75 | public function testNonAthleticNoNamespace() 76 | { 77 | $path = vfsStream::url('root\testNonAthleticNoNamespace.php'); 78 | $parser = new Parser($path); 79 | $this->assertFalse($parser->isAthleticEvent()); 80 | $this->assertEquals('\Class1', $parser->getFQN()); 81 | $this->assertEquals('vfs://root/testNonAthleticNoNamespace.php', $parser->getPath()); 82 | } 83 | 84 | 85 | public function testAthleticWithNamespaceImplements() 86 | { 87 | $path = vfsStream::url('root\testAthleticWithNamespaceImplements.php'); 88 | $parser = new Parser($path); 89 | $this->assertTrue($parser->isAthleticEvent()); 90 | $this->assertEquals('Vendor\Package\Child1\Class1', $parser->getFQN()); 91 | $this->assertEquals('vfs://root/testAthleticWithNamespaceImplements.php', $parser->getPath()); 92 | } 93 | 94 | 95 | public function testNonAthleticWithNamespaceImplements() 96 | { 97 | $path = vfsStream::url('root\testNonAthleticWithNamespaceImplements.php'); 98 | $parser = new Parser($path); 99 | $this->assertFalse($parser->isAthleticEvent()); 100 | $this->assertEquals('Vendor\Package\Child1\Class1', $parser->getFQN()); 101 | $this->assertEquals('vfs://root/testNonAthleticWithNamespaceImplements.php', $parser->getPath()); 102 | } 103 | 104 | 105 | public function testAthleticNoNamespaceImplements() 106 | { 107 | $path = vfsStream::url('root\testAthleticNoNamespaceImplements.php'); 108 | $parser = new Parser($path); 109 | $this->assertTrue($parser->isAthleticEvent()); 110 | $this->assertEquals('\Class1', $parser->getFQN()); 111 | $this->assertEquals('vfs://root/testAthleticNoNamespaceImplements.php', $parser->getPath()); 112 | } 113 | 114 | 115 | public function testNonAthleticNoNamespaceImplements() 116 | { 117 | $path = vfsStream::url('root\testNonAthleticNoNamespaceImplements.php'); 118 | $parser = new Parser($path); 119 | $this->assertFalse($parser->isAthleticEvent()); 120 | $this->assertEquals('\Class1', $parser->getFQN()); 121 | $this->assertEquals('vfs://root/testNonAthleticNoNamespaceImplements.php', $parser->getPath()); 122 | } 123 | 124 | 125 | public function testNoClass() 126 | { 127 | $path = vfsStream::url('root\testNoClass.php'); 128 | $parser = new Parser($path); 129 | $this->assertFalse($parser->isAthleticEvent()); 130 | $this->assertEmpty($parser->getFQN()); 131 | $this->assertEquals('vfs://root/testNoClass.php', $parser->getPath()); 132 | } 133 | 134 | 135 | } -------------------------------------------------------------------------------- /tests/Athletic/Discovery/RecursiveFileLoaderTest.php: -------------------------------------------------------------------------------- 1 | root = vfsStream::setup('root'); 25 | } 26 | 27 | 28 | public function tearDown() 29 | { 30 | m::close(); 31 | } 32 | 33 | 34 | public function testLoadThreeClassesFromPath() 35 | { 36 | $class1 = ' array( 42 | 'Package' => array( 43 | 'Child1' => array( 44 | 'Class1.php' => $class1 45 | ), 46 | 'Child2' => array( 47 | 'Class2.php' => $class2, 48 | 'Class3.php' => $class3 49 | ) 50 | ) 51 | ) 52 | ); 53 | 54 | vfsStream::create($structure, $this->root); 55 | $path = vfsStream::url('root\threeClasses\Package'); 56 | 57 | $mockParser1 = m::mock('\Athletic\Discovery\Parser') 58 | ->shouldReceive('isAthleticEvent') 59 | ->once() 60 | ->andReturn(true) 61 | ->getMock() 62 | ->shouldReceive('getFQN') 63 | ->once() 64 | ->andReturn('Vendor\Package\Child1\Class1') 65 | ->getMock(); 66 | 67 | $mockParser2 = m::mock('\Athletic\Discovery\Parser') 68 | ->shouldReceive('isAthleticEvent') 69 | ->once() 70 | ->andReturn(true) 71 | ->getMock() 72 | ->shouldReceive('getFQN') 73 | ->once() 74 | ->andReturn('Vendor\Package\Child2\Class2') 75 | ->getMock(); 76 | 77 | $mockParser3 = m::mock('\Athletic\Discovery\Parser') 78 | ->shouldReceive('isAthleticEvent') 79 | ->once() 80 | ->andReturn(true) 81 | ->getMock() 82 | ->shouldReceive('getFQN') 83 | ->once() 84 | ->andReturn('Vendor\Package\Child2\Class3') 85 | ->getMock(); 86 | 87 | $mockParserFactory = m::mock('\Athletic\Factories\ParserFactory') 88 | ->shouldReceive('create') 89 | ->once() 90 | ->with('vfs://root/threeClasses/Package/Child1/Class1.php') 91 | ->andReturn($mockParser1) 92 | ->getMock(); 93 | 94 | $mockParserFactory->shouldReceive('create') 95 | ->once() 96 | ->with('vfs://root/threeClasses/Package/Child2/Class2.php') 97 | ->andReturn($mockParser2) 98 | ->getMock(); 99 | 100 | $mockParserFactory->shouldReceive('create') 101 | ->once() 102 | ->with('vfs://root/threeClasses/Package/Child2/Class3.php') 103 | ->andReturn($mockParser3) 104 | ->getMock(); 105 | 106 | $fileLoader = new RecursiveFileLoader($mockParserFactory, $path); 107 | $classes = $fileLoader->getClasses(); 108 | 109 | $expectedClasses = array( 110 | 'Vendor\Package\Child1\Class1', 111 | 'Vendor\Package\Child2\Class2', 112 | 'Vendor\Package\Child2\Class3', 113 | ); 114 | 115 | $this->assertEquals($expectedClasses, $classes); 116 | } 117 | 118 | 119 | public function testLoadThreeClassesFromPathNoneAreAthletic() 120 | { 121 | $structure = array( 122 | 'threeClasses' => array( 123 | 'Package' => array( 124 | 'Child1' => array( 125 | 'Class1.php' => '' 126 | ), 127 | 'Child2' => array( 128 | 'Class2.php' => '', 129 | 'Class3.php' => '' 130 | ) 131 | ) 132 | ) 133 | ); 134 | 135 | vfsStream::create($structure, $this->root); 136 | $path = vfsStream::url('root\threeClasses\Package'); 137 | 138 | $mockParser = m::mock('\Athletic\Discovery\Parser') 139 | ->shouldReceive('isAthleticEvent') 140 | ->times(3) 141 | ->andReturn(false) 142 | ->getMock(); 143 | 144 | $mockParserFactory = m::mock('\Athletic\Factories\ParserFactory') 145 | ->shouldReceive('create') 146 | ->once() 147 | ->with('vfs://root/threeClasses/Package/Child1/Class1.php') 148 | ->andReturn($mockParser) 149 | ->getMock(); 150 | 151 | $mockParserFactory->shouldReceive('create') 152 | ->once() 153 | ->with('vfs://root/threeClasses/Package/Child2/Class2.php') 154 | ->andReturn($mockParser) 155 | ->getMock(); 156 | 157 | $mockParserFactory->shouldReceive('create') 158 | ->once() 159 | ->with('vfs://root/threeClasses/Package/Child2/Class3.php') 160 | ->andReturn($mockParser) 161 | ->getMock(); 162 | 163 | $fileLoader = new RecursiveFileLoader($mockParserFactory, $path); 164 | $classes = $fileLoader->getClasses(); 165 | 166 | $this->assertEmpty($classes); 167 | } 168 | 169 | /** 170 | * Checks if the loader accepts a file path as target. 171 | */ 172 | public function testLoadClassFromFile() 173 | { 174 | $class = ' array( 178 | 'Package' => array( 179 | 'Test.php' => $class 180 | ) 181 | ) 182 | ); 183 | 184 | vfsStream::create($structure, $this->root); 185 | $path = vfsStream::url('root/src/Package/Test.php'); 186 | 187 | $mockParser = m::mock('\Athletic\Discovery\Parser') 188 | ->shouldReceive('isAthleticEvent') 189 | ->once() 190 | ->andReturn(true) 191 | ->getMock() 192 | ->shouldReceive('getFQN') 193 | ->once() 194 | ->andReturn('Vendor\Package\Test') 195 | ->getMock(); 196 | 197 | $mockParserFactory = m::mock('\Athletic\Factories\ParserFactory') 198 | ->shouldReceive('create') 199 | ->once() 200 | ->with('vfs://root/src/Package/Test.php') 201 | ->andReturn($mockParser) 202 | ->getMock(); 203 | 204 | $fileLoader = new RecursiveFileLoader($mockParserFactory, $path); 205 | $classes = $fileLoader->getClasses(); 206 | 207 | $expectedClasses = array( 208 | 'Vendor\Package\Test' 209 | ); 210 | $this->assertEquals($expectedClasses, $classes); 211 | } 212 | 213 | } -------------------------------------------------------------------------------- /tests/Athletic/Formatters/DefaultFormatterTest.php: -------------------------------------------------------------------------------- 1 | methodName = 'testName'; 29 | $mockMethodResult->avg = 5; 30 | $mockMethodResult->min = 5; 31 | $mockMethodResult->max = 5; 32 | $mockMethodResult->sum = 5; 33 | $mockMethodResult->iterations = 5; 34 | $mockMethodResult->ops = 5; 35 | 36 | 37 | $mockMethodResults = new ArrayIterator(array($mockMethodResult)); 38 | 39 | $mockClassResult = m::mock('\Athletic\Results\ClassResults') 40 | ->shouldReceive('getClassName') 41 | ->andReturn('testClass') 42 | ->getMock() 43 | ->shouldReceive('getIterator') 44 | ->andReturn($mockMethodResults) 45 | ->getMock(); 46 | 47 | $suiteResults[] = $mockClassResult; 48 | 49 | 50 | $formatter = new DefaultFormatter(); 51 | $ret = $formatter->getFormattedResults($suiteResults); 52 | 53 | $expected = <<assertEquals($expected, $ret); 65 | } 66 | 67 | 68 | public function testOneClassThreeMethods() 69 | { 70 | /** @var MethodResults $mockMethodResult */ 71 | $mockMethodResult = m::mock('\Athletic\Results\MethodResults'); 72 | $mockMethodResult->methodName = 'testName'; 73 | $mockMethodResult->avg = 5; 74 | $mockMethodResult->min = 5; 75 | $mockMethodResult->max = 5; 76 | $mockMethodResult->sum = 5; 77 | $mockMethodResult->iterations = 5; 78 | $mockMethodResult->ops = 5; 79 | 80 | 81 | $mockMethodResults = new ArrayIterator(array($mockMethodResult, $mockMethodResult, $mockMethodResult)); 82 | 83 | $mockClassResult = m::mock('\Athletic\Results\ClassResults') 84 | ->shouldReceive('getClassName') 85 | ->andReturn('testClass') 86 | ->getMock() 87 | ->shouldReceive('getIterator') 88 | ->andReturn($mockMethodResults) 89 | ->getMock(); 90 | 91 | $suiteResults[] = $mockClassResult; 92 | 93 | 94 | $formatter = new DefaultFormatter(); 95 | $ret = $formatter->getFormattedResults($suiteResults); 96 | 97 | $expected = <<assertEquals($expected, $ret); 111 | } 112 | 113 | 114 | public function testThreeClassOneMethod() 115 | { 116 | /** @var MethodResults $mockMethodResult */ 117 | $mockMethodResult = m::mock('\Athletic\Results\MethodResults'); 118 | $mockMethodResult->methodName = 'testName'; 119 | $mockMethodResult->avg = 5; 120 | $mockMethodResult->min = 5; 121 | $mockMethodResult->max = 5; 122 | $mockMethodResult->sum = 5; 123 | $mockMethodResult->iterations = 5; 124 | $mockMethodResult->ops = 5; 125 | 126 | 127 | $mockMethodResults = new ArrayIterator(array($mockMethodResult)); 128 | 129 | $mockClassResult = m::mock('\Athletic\Results\ClassResults') 130 | ->shouldReceive('getClassName') 131 | ->andReturn('testClass') 132 | ->getMock() 133 | ->shouldReceive('getIterator') 134 | ->andReturn($mockMethodResults) 135 | ->getMock(); 136 | 137 | $suiteResults = array($mockClassResult, $mockClassResult, $mockClassResult); 138 | 139 | 140 | $formatter = new DefaultFormatter(); 141 | $ret = $formatter->getFormattedResults($suiteResults); 142 | 143 | $expected = <<assertEquals($expected, $ret); 167 | } 168 | } -------------------------------------------------------------------------------- /tests/Athletic/Formatters/GroupedFormatterTest.php: -------------------------------------------------------------------------------- 1 | methodName = 'testName'; 30 | $mockMethodResult->avg = 5; 31 | $mockMethodResult->min = 5; 32 | $mockMethodResult->max = 5; 33 | $mockMethodResult->sum = 5; 34 | $mockMethodResult->iterations = 5; 35 | $mockMethodResult->ops = 5; 36 | $mockMethodResult->group = 'Group1'; 37 | $mockMethodResult->baseline = true; 38 | 39 | 40 | $mockMethodResults = new ArrayIterator(array($mockMethodResult)); 41 | 42 | $mockClassResult = m::mock('\Athletic\Results\ClassResults') 43 | ->shouldReceive('getClassName') 44 | ->andReturn('testClass') 45 | ->getMock() 46 | ->shouldReceive('getIterator') 47 | ->andReturn($mockMethodResults) 48 | ->getMock(); 49 | 50 | $suiteResults[] = $mockClassResult; 51 | 52 | 53 | $formatter = new GroupedFormatter(); 54 | $ret = $formatter->getFormattedResults($suiteResults); 55 | 56 | $expected = <<assertEquals($expected, $ret); 68 | } 69 | 70 | 71 | /** 72 | * @expectedException Athletic\Common\Exceptions\OnlyOneBaselineAllowedException 73 | */ 74 | public function testOneClassThreeBaselines() 75 | { 76 | /** @var MethodResults $mockMethodResult */ 77 | $mockMethodResult = m::mock('\Athletic\Results\MethodResults'); 78 | $mockMethodResult->methodName = 'testName'; 79 | $mockMethodResult->avg = 5; 80 | $mockMethodResult->min = 5; 81 | $mockMethodResult->max = 5; 82 | $mockMethodResult->sum = 5; 83 | $mockMethodResult->iterations = 5; 84 | $mockMethodResult->ops = 5; 85 | $mockMethodResult->group = 'Group1'; 86 | $mockMethodResult->baseline = true; 87 | 88 | 89 | $mockMethodResults = new ArrayIterator(array($mockMethodResult, $mockMethodResult, $mockMethodResult)); 90 | 91 | $mockClassResult = m::mock('\Athletic\Results\ClassResults') 92 | ->shouldReceive('getClassName') 93 | ->andReturn('testClass') 94 | ->getMock() 95 | ->shouldReceive('getIterator') 96 | ->andReturn($mockMethodResults) 97 | ->getMock(); 98 | 99 | $suiteResults[] = $mockClassResult; 100 | 101 | 102 | $formatter = new GroupedFormatter(); 103 | $ret = $formatter->getFormattedResults($suiteResults); 104 | 105 | } 106 | 107 | public function testOneClassThreeTests() 108 | { 109 | /** @var MethodResults $mockMethodResult */ 110 | $mockMethodResult = m::mock('\Athletic\Results\MethodResults'); 111 | $mockMethodResult->methodName = 'testName'; 112 | $mockMethodResult->avg = 5; 113 | $mockMethodResult->min = 5; 114 | $mockMethodResult->max = 5; 115 | $mockMethodResult->sum = 5; 116 | $mockMethodResult->iterations = 5; 117 | $mockMethodResult->ops = 5; 118 | $mockMethodResult->group = 'Group1'; 119 | $mockMethodResult->baseline = true; 120 | 121 | /** @var MethodResults $mockMethodResult */ 122 | $mockMethodResult2 = m::mock('\Athletic\Results\MethodResults'); 123 | $mockMethodResult2->methodName = 'testName'; 124 | $mockMethodResult2->avg = 3; 125 | $mockMethodResult2->min = 3; 126 | $mockMethodResult2->max = 5; 127 | $mockMethodResult2->sum = 5; 128 | $mockMethodResult2->iterations = 5; 129 | $mockMethodResult2->ops = 5; 130 | $mockMethodResult2->group = 'Group1'; 131 | $mockMethodResult2->baseline = false; 132 | 133 | 134 | $mockMethodResults = new ArrayIterator(array($mockMethodResult, $mockMethodResult2, $mockMethodResult2)); 135 | 136 | $mockClassResult = m::mock('\Athletic\Results\ClassResults') 137 | ->shouldReceive('getClassName') 138 | ->andReturn('testClass') 139 | ->getMock() 140 | ->shouldReceive('getIterator') 141 | ->andReturn($mockMethodResults) 142 | ->getMock(); 143 | 144 | $suiteResults[] = $mockClassResult; 145 | 146 | 147 | $formatter = new GroupedFormatter(); 148 | $ret = $formatter->getFormattedResults($suiteResults); 149 | $expected = <<assertEquals($expected, $ret); 163 | 164 | } 165 | 166 | 167 | public function testThreeClassOneMethod() 168 | { 169 | /** @var MethodResults $mockMethodResult */ 170 | $mockMethodResult = m::mock('\Athletic\Results\MethodResults'); 171 | $mockMethodResult->methodName = 'testName'; 172 | $mockMethodResult->avg = 5; 173 | $mockMethodResult->min = 5; 174 | $mockMethodResult->max = 5; 175 | $mockMethodResult->sum = 5; 176 | $mockMethodResult->iterations = 5; 177 | $mockMethodResult->ops = 5; 178 | $mockMethodResult->group = 'Group1'; 179 | $mockMethodResult->baseline = true; 180 | 181 | /** @var MethodResults $mockMethodResult2 */ 182 | $mockMethodResult2 = m::mock('\Athletic\Results\MethodResults'); 183 | $mockMethodResult2->methodName = 'testName'; 184 | $mockMethodResult2->avg = 5; 185 | $mockMethodResult2->min = 5; 186 | $mockMethodResult2->max = 5; 187 | $mockMethodResult2->sum = 5; 188 | $mockMethodResult2->iterations = 5; 189 | $mockMethodResult2->ops = 5; 190 | $mockMethodResult2->group = 'Group2'; 191 | $mockMethodResult2->baseline = true; 192 | 193 | /** @var MethodResults $mockMethodResult3 */ 194 | $mockMethodResult3 = m::mock('\Athletic\Results\MethodResults'); 195 | $mockMethodResult3->methodName = 'testName'; 196 | $mockMethodResult3->avg = 5; 197 | $mockMethodResult3->min = 5; 198 | $mockMethodResult3->max = 5; 199 | $mockMethodResult3->sum = 5; 200 | $mockMethodResult3->iterations = 5; 201 | $mockMethodResult3->ops = 5; 202 | $mockMethodResult3->group = 'Group3'; 203 | $mockMethodResult3->baseline = true; 204 | 205 | $mockMethodResults = new ArrayIterator(array($mockMethodResult)); 206 | $mockMethodResults2 = new ArrayIterator(array($mockMethodResult2)); 207 | $mockMethodResults3 = new ArrayIterator(array($mockMethodResult3)); 208 | 209 | $mockClassResult = m::mock('\Athletic\Results\ClassResults') 210 | ->shouldReceive('getClassName') 211 | ->andReturn('testClass') 212 | ->getMock() 213 | ->shouldReceive('getIterator') 214 | ->andReturn($mockMethodResults) 215 | ->getMock(); 216 | 217 | $mockClassResult2 = m::mock('\Athletic\Results\ClassResults') 218 | ->shouldReceive('getClassName') 219 | ->andReturn('testClass2') 220 | ->getMock() 221 | ->shouldReceive('getIterator') 222 | ->andReturn($mockMethodResults2) 223 | ->getMock(); 224 | 225 | $mockClassResult3 = m::mock('\Athletic\Results\ClassResults') 226 | ->shouldReceive('getClassName') 227 | ->andReturn('testClass3') 228 | ->getMock() 229 | ->shouldReceive('getIterator') 230 | ->andReturn($mockMethodResults3) 231 | ->getMock(); 232 | 233 | $suiteResults = array($mockClassResult, $mockClassResult2, $mockClassResult3); 234 | 235 | 236 | $formatter = new GroupedFormatter(); 237 | $ret = $formatter->getFormattedResults($suiteResults); 238 | 239 | $expected = <<assertEquals($expected, $ret); 265 | } 266 | } -------------------------------------------------------------------------------- /tests/Athletic/Formatters/JsonFormatterTest.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class JsonFormatterTest extends \PHPUnit_Framework_TestCase 19 | { 20 | /** 21 | * Tested class FQCN for mocking. 22 | * 23 | * @type string 24 | * @since 0.1.0 25 | */ 26 | protected $testedClass = 'Athletic\Formatters\JsonFormatter'; 27 | /** 28 | * MethodResults FQCN for mocking. 29 | * 30 | * @type string 31 | * @since 0.1.0 32 | */ 33 | protected $methodResultsClass = 'Athletic\Results\MethodResults'; 34 | /** 35 | * ClassResults FQCN for mocking. 36 | * 37 | * @type string 38 | * @since 0.1.0 39 | */ 40 | protected $classResultsClass = 'Athletic\Results\ClassResults'; 41 | /** 42 | * Some static data to mock Athletic output. 43 | * 44 | * @type array 45 | * @since 0.1.0 46 | */ 47 | protected $staticData = array( 48 | 'ClassA' => array( 49 | 'A' => array( 50 | 'iterations' => 1000, 51 | 'sum' => 10, 52 | 'min' => 0.005, 53 | 'max' => 0.015, 54 | 'avg' => 0.01, 55 | 'ops' => 100, 56 | 'group' => null, 57 | ), 58 | 'B' => array( 59 | 'iterations' => 1000, 60 | 'sum' => 10, 61 | 'min' => 0.005, 62 | 'max' => 0.015, 63 | 'avg' => 0.01, 64 | 'ops' => 100, 65 | 'group' => null, 66 | ), 67 | 'C' => array( 68 | 'iterations' => 1000, 69 | 'sum' => 10, 70 | 'min' => 0.005, 71 | 'max' => 0.015, 72 | 'avg' => 0.01, 73 | 'ops' => 100, 74 | 'group' => 'A', 75 | ), 76 | 'D' => array( 77 | 'iterations' => 1000, 78 | 'sum' => 10, 79 | 'min' => 0.005, 80 | 'max' => 0.015, 81 | 'avg' => 0.01, 82 | 'ops' => 100, 83 | 'group' => 'B', 84 | ), 85 | ), 86 | 'ClassB' => array( 87 | 'E' => array( 88 | 'iterations' => 1000, 89 | 'sum' => 10, 90 | 'min' => 0.005, 91 | 'max' => 0.015, 92 | 'avg' => 0.01, 93 | 'ops' => 100, 94 | 'group' => null, 95 | ), 96 | ) 97 | ); 98 | 99 | /** 100 | * This is **not** a PHPUnit data provider. Deal with it. 101 | * 102 | * Also it is messed as hell -_-. 103 | * 104 | * @return ClassResults[] Mocked Athletic results. 105 | * @since 0.1.0 106 | */ 107 | public function resultsProvider() 108 | { 109 | $mockedOutput = array(); 110 | foreach ($this->staticData as $className => $classStats) { 111 | $results = array(); 112 | foreach ($classStats as $methodName => $methodStats) { 113 | /** @type MethodResults $methodResults */ 114 | $methodResults = Mockery::mock($this->methodResultsClass); 115 | $methodResults->methodName = $methodName; 116 | foreach ($methodStats as $key => $value) { 117 | $methodResults->$key = $value; 118 | } 119 | $results[] = $methodResults; 120 | } 121 | /** @type ClassResults $classResults */ 122 | $classResults = Mockery::mock( 123 | $this->classResultsClass, 124 | array($className, $results) 125 | ) 126 | ->shouldReceive('getClassName') 127 | ->andReturn($className) 128 | ->getMock() 129 | ->shouldReceive('getIterator') 130 | ->andReturn(new \ArrayIterator($results)) 131 | ->getMock(); 132 | $mockedOutput[] = $classResults; 133 | } 134 | return $mockedOutput; 135 | } 136 | 137 | /** 138 | * This is **not** a PHPUnit data provider. Deal with it. 139 | * 140 | * @return array Expected JsonFormatter output for mixed strategy. 141 | * @since 0.1.0 142 | */ 143 | protected function mixedMethodsResultsProvider() 144 | { 145 | $data = $this->staticData; 146 | foreach ($data as $className => $methodResults) { 147 | $groups = array(); 148 | $methods = array(); 149 | foreach ($methodResults as $methodName => $stats) { 150 | $group = $stats['group']; 151 | unset($stats['group']); 152 | if ($group) { 153 | if (!isset($groups[$group])) { 154 | $groups[$group] = array(); 155 | } 156 | $groups[$group][$methodName] = $stats; 157 | } else { 158 | $methods[$methodName] = $stats; 159 | } 160 | } 161 | $data[$className] = array( 162 | 'groups' => $groups, 163 | 'methods' => $methods 164 | ); 165 | } 166 | return $data; 167 | } 168 | 169 | /** 170 | * This is **not** a PHPUnit data provider. Deal with it. 171 | * 172 | * @return array Expected JsonFormatter output for grouped strategy. 173 | * @since 0.1.0 174 | */ 175 | protected function groupedMethodsResultsProvider() 176 | { 177 | $data = $this->mixedMethodsResultsProvider(); 178 | foreach ($data as $className => $methodResults) { 179 | $data[$className] = $methodResults['groups']; 180 | } 181 | return $data; 182 | } 183 | 184 | /** 185 | * This is **not** a PHPUnit data provider. Deal with it. 186 | * 187 | * @return array Expected JsonFormatter output for nongrouped strategy. 188 | * @since 0.1.0 189 | */ 190 | protected function plainMethodsResultsProvider() 191 | { 192 | $data = $this->mixedMethodsResultsProvider(); 193 | foreach ($data as $className => $methodResults) { 194 | $data[$className] = $methodResults['methods']; 195 | } 196 | return $data; 197 | } 198 | 199 | // tests 200 | 201 | /** 202 | * Tests output for various strategies. 203 | * 204 | * @return void 205 | * @since 0.1.0 206 | */ 207 | public function testStrategies() 208 | { 209 | /** @type JsonFormatter $formatter */ 210 | //$formatter = Mockery::mock($this->testedClass); 211 | $formatter = new JsonFormatter(); 212 | $results = $this->resultsProvider(); 213 | 214 | // 'show as plain list' strategy 215 | $formatted = $formatter->getFormattedResults($results); 216 | $this->assertSame(json_decode($formatted, true), $this->staticData); 217 | 218 | // 'show both plain methods and groups' strategy 219 | $formatted = $formatter->getFormattedResults( 220 | $results, 221 | JsonFormatter::STRATEGY_MIX_VIEWS 222 | ); 223 | $this->assertEquals( 224 | json_decode($formatted, true), 225 | $this->mixedMethodsResultsProvider() 226 | ); 227 | 228 | // 'show only groups' strategy 229 | $formatted = $formatter->getFormattedResults( 230 | $results, 231 | JsonFormatter::STRATEGY_SHOW_GROUPED 232 | ); 233 | $this->assertEquals( 234 | json_decode($formatted, true), 235 | $this->groupedMethodsResultsProvider() 236 | ); 237 | 238 | // 'show only methods without groups' strategy 239 | $formatted = $formatter->getFormattedResults( 240 | $results, 241 | JsonFormatter::STRATEGY_SHOW_NONGROUPED 242 | ); 243 | $this->assertEquals( 244 | json_decode($formatted, true), 245 | $this->plainMethodsResultsProvider() 246 | ); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /tests/Athletic/Publishers/StdOutPublisherTest.php: -------------------------------------------------------------------------------- 1 | shouldReceive('getFormattedResults') 27 | ->with($mockResults) 28 | ->andReturn($output) 29 | ->getMock(); 30 | 31 | $publisher = new StdOutPublisher($mockFormatter); 32 | $publisher->publish($mockResults); 33 | 34 | $this->expectOutputString($output); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /tests/Athletic/Results/MethodResultsTest.php: -------------------------------------------------------------------------------- 1 | setExpectedException(null); 32 | new MethodResults('fastBenchmark', $results, count($results)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /tests/Athletic/Runners/ClassRunnerTest.php: -------------------------------------------------------------------------------- 1 | root = vfsStream::setup('root'); 24 | } 25 | 26 | 27 | public function tearDown() 28 | { 29 | m::close(); 30 | } 31 | 32 | 33 | public function testConstructor() 34 | { 35 | $class = 'abc'; 36 | $mockMethodFactory = m::mock('\Athletic\Factories\MethodResultsFactory'); 37 | $classRunner = new ClassRunner($mockMethodFactory, $class); 38 | } 39 | 40 | 41 | /** 42 | * @expectedException ReflectionException 43 | */ 44 | public function testRunWithNonExistantClass() 45 | { 46 | $class = 'abc'; 47 | $mockMethodFactory = m::mock('\Athletic\Factories\MethodResultsFactory'); 48 | $classRunner = new ClassRunner($mockMethodFactory, $class); 49 | 50 | $classRunner->run(); 51 | } 52 | 53 | 54 | public function testRunWithAthleticClass() 55 | { 56 | 57 | $classFile = <<<'EOF' 58 | 'value'); 73 | } 74 | 75 | } 76 | EOF; 77 | 78 | 79 | $structure = array( 80 | 'classRunner' => array( 81 | 'testRunWithAthleticClass.php' => $classFile 82 | ) 83 | ); 84 | 85 | vfsStream::create($structure, $this->root); 86 | $path = vfsStream::url('root\classRunner\testRunWithAthleticClass.php'); 87 | 88 | include_once($path); 89 | 90 | $class = 'Athletic\Tests\Runners\TestRunWithAthleticClass'; 91 | 92 | $mockMethodFactory = m::mock('\Athletic\Factories\MethodResultsFactory'); 93 | $classRunner = new ClassRunner($mockMethodFactory, $class); 94 | 95 | $ret = $classRunner->run(); 96 | $expected = array('field' => 'value'); 97 | 98 | $this->expectOutputString("setMethodFactory\nrun\n"); 99 | $this->assertEquals($expected, $ret); 100 | } 101 | 102 | 103 | public function testRunWithNonAthleticClass() 104 | { 105 | 106 | $classFile = <<<'EOF' 107 | 'value'); 122 | } 123 | 124 | } 125 | EOF; 126 | 127 | 128 | $structure = array( 129 | 'classRunner' => array( 130 | 'testRunWithNonAthleticClass.php' => $classFile 131 | ) 132 | ); 133 | 134 | vfsStream::create($structure, $this->root); 135 | $path = vfsStream::url('root\classRunner\testRunWithNonAthleticClass.php'); 136 | 137 | include_once($path); 138 | 139 | $class = 'Athletic\Tests\Runners\TestRunWithNonAthleticClass'; 140 | 141 | $mockMethodFactory = m::mock('\Athletic\Factories\MethodResultsFactory'); 142 | $classRunner = new ClassRunner($mockMethodFactory, $class); 143 | 144 | $ret = $classRunner->run(); 145 | 146 | $this->expectOutputString(""); 147 | $this->assertEmpty($ret); 148 | } 149 | } -------------------------------------------------------------------------------- /tests/Athletic/TestAsset/RunsCounter.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @package Athletic\TestAsset 18 | */ 19 | class RunsCounter extends AthleticEvent 20 | { 21 | /** 22 | * @var int 23 | */ 24 | public $runs = 0; 25 | 26 | /** 27 | * @var int 28 | */ 29 | public $setUps = 0; 30 | 31 | /** 32 | * @var int 33 | */ 34 | public $tearDowns = 0; 35 | 36 | /** 37 | * {@inheritDoc} 38 | */ 39 | public function setUp() 40 | { 41 | $this->setUps += 1; 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | public function tearDown() 48 | { 49 | $this->tearDowns += 1; 50 | } 51 | 52 | /** 53 | * @iterations 5 54 | */ 55 | public function testRuns() 56 | { 57 | $this->runs += 1; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('Athletic\\', __DIR__); 26 | 27 | unset($files, $file, $loader); --------------------------------------------------------------------------------