├── .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 | | [](https://packagist.org/packages/athletic/athletic) | [](https://travis-ci.org/polyfractal/athletic) | [](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);
--------------------------------------------------------------------------------