Skip to content

Commit

Permalink
Merge pull request #3 from xepozz/tracing
Browse files Browse the repository at this point in the history
Tracing calls
  • Loading branch information
xepozz authored Feb 24, 2024
2 parents aac7c60 + be8669e commit cb92342
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 14 deletions.
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ functions as: `time()`, `str_contains()`, `rand`, etc.
[![Total Downloads](https://poser.pugx.org/xepozz/internal-mocker/downloads.svg)](https://packagist.org/packages/xepozz/internal-mocker)
[![phpunit](https://github.com/xepozz/internal-mocker/workflows/PHPUnit/badge.svg)](https://github.com/xepozz/internal-mocker/actions)

# Table of contents

- [Installation](#installation)
- [Usage](#usage)
- [Register a hook](#register-a-hook)
- [Register mocks](#register-mocks)
- [Runtime mocks](#runtime-mocks)
- [Pre-defined mock](#pre-defined-mock)
- [Mix of two previous ways](#mix-of-two-previous-ways)
- [State](#state)
- [Tracking calls](#tracking-calls)
- [Global namespaced functions](#global-namespaced-functions)
- [Internal functions](#internal-functions)
- [Workaround](#workaround)
- [Internal function implementation](#internal-function-implementation)
- [Restrictions](#restrictions)
- [Data Providers](#data-providers)

## Installation

```bash
Expand Down Expand Up @@ -166,6 +184,27 @@ These methods save "current" state and unload each `Runtime mock` mock that was

Using `MockerState::saveState()` after `Mocker->load($mocks)` saves only **_Pre-defined_** mocks.

### Tracking calls

You may track calls of mocked functions by using `MockerState::getTraces()` method.

```php
$traces = MockerState::getTraces('App\Service', 'time');
```

`$traces` will contain an array of arrays with the following structure:

```php
[
[
'arguments' => [], // arguments of the function
'trace' => [], // the result of debug_backtrace function
'result' => 1708764835, // result of the function
],
// ...
]
```

## Global namespaced functions

### Internal functions
Expand All @@ -186,6 +225,19 @@ The best way is to disable them only for tests by running a command with the add
php -ddisable_functions=${functions} ./vendor/bin/phpunit
```

> If you are using PHPStorm you may set the command in the `Run/Debug Configurations` section.
> Add the flag `-ddisable_functions=${functions}` to the `Interpreter options` field.
> You may keep the command in the `composer.json` file under the `scripts` section.
```json
{
"scripts": {
"test": "php -ddisable_functions=time,serialize,header,date ./vendor/bin/phpunit"
}
}
```

> Replace `${functions}` with the list of functions that you want to mock, separated by commas, e.g.: `time,rand`.
So now you can mock global functions as well.
Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@
"psr-4": {
"Xepozz\\InternalMocker\\Tests\\": "tests/"
}
},
"scripts": {
"test": "php -ddisable_functions=time vendor/bin/phpunit"
}
}
16 changes: 10 additions & 6 deletions src/Mocker.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function generate(array $mocks): string
$defaultString = $imock['default'] ? 'true' : 'false';
$mockerConfig[] = <<<PHP
MockerState::addCondition(
"$namespace",
"$namespace",
"$functionName",
$argumentsString,
$resultString,
Expand All @@ -60,7 +60,7 @@ public function generate(array $mocks): string
$outputs[] = <<<PHP
namespace {$namespace} {
use {$mockerConfigClassName};
$innerOutputsString
}
PHP;
Expand All @@ -73,7 +73,7 @@ public function generate(array $mocks): string
$pre = <<<PHP
namespace {
use {$mockerConfigClassName};
{$runtimeMocks}
}
PHP;
Expand Down Expand Up @@ -115,10 +115,14 @@ private function generateFunction(mixed $groupedMocks): string
$string = <<<PHP
function $functionName(...\$arguments)
{
\$position = MockerState::saveTrace(__NAMESPACE__, "$functionName", \$arguments);
if (MockerState::checkCondition(__NAMESPACE__, "$functionName", \$arguments)) {
return MockerState::getResult(__NAMESPACE__, "$functionName", \$arguments);
\$result = MockerState::getResult(__NAMESPACE__, "$functionName", \$arguments);
} else {
\$result = MockerState::getDefaultResult(__NAMESPACE__, "$functionName", $function);
}
return MockerState::getDefaultResult(__NAMESPACE__, "$functionName", $function);
return MockerState::saveTraceResult(__NAMESPACE__, "$functionName", \$position, \$result);
}
PHP;
$innerOutputs[] = $string;
Expand All @@ -131,4 +135,4 @@ public function getConfigPath(): string
{
return $this->path;
}
}
}
36 changes: 35 additions & 1 deletion src/MockerState.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

final class MockerState
{
private static mixed $traces = [];
private static array $savedState = [];
private static array $state = [];
private static array $defaults = [];
Expand Down Expand Up @@ -106,5 +107,38 @@ public static function saveState(): void
public static function resetState(): void
{
self::$state = self::$savedState;
self::$traces = [];
}
}

public static function saveTrace(
string $namespace,
string $functionName,
array $arguments
): int {
$position = count(self::$traces[$namespace][$functionName] ?? []);
self::$traces[$namespace][$functionName][$position] = [
'arguments' => $arguments,
'trace' => debug_backtrace(),
];

return $position;
}

public static function saveTraceResult(
string $namespace,
string $functionName,
int $position,
mixed $result
): mixed {
self::$traces[$namespace][$functionName][$position]['result'] = $result;

return $result;
}

public static function getTraces(
string $namespace,
string $functionName
): array {
return self::$traces[$namespace][$functionName] ?? [];
}
}
84 changes: 84 additions & 0 deletions tests/Integration/TraceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Xepozz\InternalMocker\Tests\Integration;

use PHPUnit\Framework\TestCase;
use Xepozz\InternalMocker\MockerState;
use Xepozz\InternalMocker\Tests\MockerExtension;

final class TraceTest extends TestCase
{
public function __construct(?string $name = null, array $data = [], $dataName = '')
{
MockerExtension::load();
parent::__construct($name, $data, $dataName);
}

public function testLogs(): void
{
$object = new UseInSucceedDataProviderStub();
$object->run('test');
$object->run('test2');
$object->run('test3');

$traces = MockerState::getTraces(
__NAMESPACE__,
'serialize',
);

$this->assertIsArray($traces);

$this->assertCount(3, $traces);

foreach ($traces as $trace) {
$this->assertArrayHasKey('arguments', $trace);
$this->assertArrayHasKey('result', $trace);

$this->assertIsArray($trace['arguments']);
$this->assertIsArray($trace['trace']);
$this->assertIsString($trace['result']);
}

$this->assertEquals(['test'], $traces[0]['arguments']);
$this->assertEquals('s:4:"test";', $traces[0]['result']);

$this->assertEquals(['test2'], $traces[1]['arguments']);
$this->assertEquals('s:5:"test2";', $traces[1]['result']);

$this->assertEquals(['test3'], $traces[2]['arguments']);
$this->assertEquals('s:5:"test3";', $traces[2]['result']);
}

public function testBacktrace(): void
{
$object = new UseInSucceedDataProviderStub();
$object->run('test');

$traces = MockerState::getTraces(
__NAMESPACE__,
'serialize',
);

$this->assertIsArray($traces);

$this->assertCount(1, $traces);

$this->assertEquals(['test'], $traces[0]['arguments']);
$this->assertEquals('saveTrace', $traces[0]['trace'][0]['function']);
$this->assertEquals(120, $traces[0]['trace'][0]['line']);
$this->assertEquals(
[
__NAMESPACE__,
'serialize',
['test'],
],
$traces[0]['trace'][0]['args'],
);

$this->assertEquals(__NAMESPACE__ . '\serialize', $traces[0]['trace'][1]['function']);
$this->assertEquals(11, $traces[0]['trace'][1]['line']);
$this->assertEquals(['test'], $traces[0]['trace'][1]['args']);
}
}
22 changes: 15 additions & 7 deletions tests/MockerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function generateProvider()
use Xepozz\InternalMocker\MockerState;
MockerState::addCondition(
"Xepozz\InternalMocker\Tests\Integration",
"Xepozz\InternalMocker\Tests\Integration",
"time",
[],
555,
Expand All @@ -49,10 +49,14 @@ public function generateProvider()
function time(...\$arguments)
{
\$position = MockerState::saveTrace(__NAMESPACE__, "time", \$arguments);
if (MockerState::checkCondition(__NAMESPACE__, "time", \$arguments)) {
return MockerState::getResult(__NAMESPACE__, "time", \$arguments);
\$result = MockerState::getResult(__NAMESPACE__, "time", \$arguments);
} else {
\$result = MockerState::getDefaultResult(__NAMESPACE__, "time", fn() => \\time(...\$arguments));
}
return MockerState::getDefaultResult(__NAMESPACE__, "time", fn() => \\time(...\$arguments));
return MockerState::saveTraceResult(__NAMESPACE__, "time", \$position, \$result);
}
}
PHP,
Expand Down Expand Up @@ -83,14 +87,14 @@ function time(...\$arguments)
use Xepozz\InternalMocker\MockerState;
MockerState::addCondition(
"Xepozz\InternalMocker\Tests\Integration",
"Xepozz\InternalMocker\Tests\Integration",
"str_contains",
['haystack' => 'string','needle' => 'str'],
false,
false,
);
MockerState::addCondition(
"Xepozz\InternalMocker\Tests\Integration",
"Xepozz\InternalMocker\Tests\Integration",
"str_contains",
['haystack' => 'string2','needle' => 'str'],
false,
Expand All @@ -104,10 +108,14 @@ function time(...\$arguments)
function str_contains(...\$arguments)
{
\$position = MockerState::saveTrace(__NAMESPACE__, "str_contains", \$arguments);
if (MockerState::checkCondition(__NAMESPACE__, "str_contains", \$arguments)) {
return MockerState::getResult(__NAMESPACE__, "str_contains", \$arguments);
\$result = MockerState::getResult(__NAMESPACE__, "str_contains", \$arguments);
} else {
\$result = MockerState::getDefaultResult(__NAMESPACE__, "str_contains", fn() => \\str_contains(...\$arguments));
}
return MockerState::getDefaultResult(__NAMESPACE__, "str_contains", fn() => \\str_contains(...\$arguments));
return MockerState::saveTraceResult(__NAMESPACE__, "str_contains", \$position, \$result);
}
}
PHP,
Expand Down

0 comments on commit cb92342

Please sign in to comment.