diff --git a/CHANGES b/CHANGES index a0505bd..50fff84 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,7 @@ +1.4.0 (Dec 14, 2023): +- Add support for a custom tracer for client methods. +- Support finer granularity on timeouts. + 1.3.0 (Nov 10, 2023): - Added in-memory evaluation cache for the duration of a request. diff --git a/examples/tracer.php b/examples/tracer.php new file mode 100644 index 0000000..b2fc176 --- /dev/null +++ b/examples/tracer.php @@ -0,0 +1,74 @@ +traces[$event['id']] ?? []; + + switch ($event['event']) { + case Tracer::EVENT_START: + $trace['start'] = microtime(true); + $trace['args'] = $event['arguments']; + break; + case Tracer::EVENT_RPC_START: + $trace['rpc_start'] = microtime(true); + break; + case Tracer::EVENT_RPC_END: + $trace['rpc_end'] = microtime(true); + break; + case Tracer::EVENT_EXCEPTION: + $trace['exception'] = $event['exception']; + break; + case Tracer::EVENT_END: + $trace['end'] = microtime(true); + break; + } + + $this->traces[$event['id']] = $trace; + } + + public function getTraces(): array + { + return $this->traces; + } +} + +$ct = new CustomTracer(); + +$factory = Factory::withConfig([ + 'transfer' => [ + 'address' => '../../splitd.sock', + 'type' => 'unix-stream', + ], + 'logging' => [ + 'level' => \Psr\Log\LogLevel::INFO, + ], + 'utils' => [ + 'tracer' => [ + 'hook' => $ct, + 'forwardArgs' => true, + ] + ], + +]); + +$manager = $factory->manager(); +$client = $factory->client(); +echo $client->getTreatment("key", null, $manager->splitNames()[0], ['age' => 22]); +var_dump($ct->getTraces()); diff --git a/src/Client.php b/src/Client.php index 8ce2418..d1a5d3c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -3,6 +3,9 @@ namespace SplitIO\ThinSdk; use \SplitIO\ThinSdk\Utils\ImpressionListener; +use \SplitIO\ThinSdk\Utils\Tracing\TracingEventFactory as TEF; +use \SplitIO\ThinSdk\Utils\Tracing\Tracer; +use \SplitIO\ThinSdk\Utils\Tracing\NoOpTracerHook; use \SplitIO\ThinSdk\Utils\EvalCache\Cache; use \SplitIO\ThinSdk\Utils\EvalCache\NoCache; use \SplitIO\ThinSdk\Utils\InputValidator\InputValidator; @@ -20,36 +23,49 @@ class Client implements ClientInterface private /*?ImpressionListener*/ $impressionListener; private /*InputValidator*/ $inputValidator; private /*Cache*/ $cache; + private /*Tracer*/ $tracer; - public function __construct(Manager $manager, LoggerInterface $logger, ?ImpressionListener $impressionListener, ?Cache $cache = null) + public function __construct(Manager $manager, LoggerInterface $logger, ?ImpressionListener $impressionListener, ?Cache $cache = null, ?Tracer $tracer = null) { $this->logger = $logger; $this->lm = $manager; $this->impressionListener = $impressionListener; $this->inputValidator = new InputValidator($logger); $this->cache = $cache ?? new NoCache(); + $this->tracer = $tracer ?? new Tracer(new NoOpTracerHook()); } public function getTreatment(string $key, ?string $bucketingKey, string $feature, ?array $attributes = null): string { try { + $id = $this->tracer->makeId(); + $method = Tracer::METHOD_GET_TREATMENT; + $this->tracer->trace(TEF::forStart($method, $id, $this->tracer->includeArgs() ? func_get_args() : [])); if (($fromCache = $this->cache->get($key, $feature, $attributes)) != null) { return $fromCache; } + $this->tracer->trace(TEF::forRPCStart($method, $id)); list($treatment, $ilData) = $this->lm->getTreatment($key, $bucketingKey, $feature, $attributes); + $this->tracer->trace(TEF::forRPCEnd($method, $id)); $this->handleListener($key, $bucketingKey, $feature, $attributes, $treatment, $ilData); $this->cache->set($key, $feature, $attributes, $treatment); return $treatment; } catch (\Exception $exc) { + $this->tracer->trace(TEF::forException($method, $id, $exc)); $this->logger->error($exc); return "control"; + } finally { + $this->tracer->trace(TEF::forEnd($method, $id)); } } public function getTreatments(string $key, ?string $bucketingKey, array $features, ?array $attributes = null): array { try { + $id = $this->tracer->makeId(); + $method = Tracer::METHOD_GET_TREATMENTS; + $this->tracer->trace(TEF::forStart($method, $id, $this->tracer->includeArgs() ? func_get_args() : [])); // try to fetch items from cache. return result if all evaluations are cached // otherwise, send a Treatments RPC for missing ones and return merged result $toReturn = $this->cache->getMany($key, $features, $attributes); @@ -58,7 +74,9 @@ public function getTreatments(string $key, ?string $bucketingKey, array $feature return $toReturn; } + $this->tracer->trace(TEF::forRPCStart($method, $id)); $results = $this->lm->getTreatments($key, $bucketingKey, $features, $attributes); + $this->tracer->trace(TEF::forRPCEnd($method, $id)); foreach ($results as $feature => $result) { list($treatment, $ilData) = $result; $toReturn[$feature] = $treatment; @@ -67,35 +85,48 @@ public function getTreatments(string $key, ?string $bucketingKey, array $feature $this->cache->setMany($key, $attributes, $toReturn); return $toReturn; } catch (\Exception $exc) { + $this->tracer->trace(TEF::forException($method, $id, $exc)); $this->logger->error($exc); return array_reduce($features, function ($r, $k) { $r[$k] = "control"; return $r; }, []); + } finally { + $this->tracer->trace(TEF::forEnd($method, $id)); } } public function getTreatmentWithConfig(string $key, ?string $bucketingKey, string $feature, ?array $attributes = null): array { try { - + $id = $this->tracer->makeId(); + $method = Tracer::METHOD_GET_TREATMENT_WITH_CONFIG; + $this->tracer->trace(TEF::forStart($method, $id, $this->tracer->includeArgs() ? func_get_args() : [])); if (($fromCache = $this->cache->getWithConfig($key, $feature, $attributes)) != null) { return $fromCache; } + $this->tracer->trace(TEF::forRPCStart($method, $id)); list($treatment, $ilData, $config) = $this->lm->getTreatmentWithConfig($key, $bucketingKey, $feature, $attributes); + $this->tracer->trace(TEF::forRPCEnd($method, $id)); $this->handleListener($key, $bucketingKey, $feature, $attributes, $treatment, $ilData); $this->cache->setWithConfig($key, $feature, $attributes, $treatment, $config); return ['treatment' => $treatment, 'config' => $config]; } catch (\Exception $exc) { + $this->tracer->trace(TEF::forException($method, $id, $exc)); $this->logger->error($exc); - return "control"; + return ['treatment' => "control", 'config' => null]; + } finally { + $this->tracer->trace(TEF::forEnd($method, $id)); } } public function getTreatmentsWithConfig(string $key, ?string $bucketingKey, array $features, ?array $attributes = null): array { try { + $id = $this->tracer->makeId(); + $method = Tracer::METHOD_GET_TREATMENTS_WITH_CONFIG; + $this->tracer->trace(TEF::forStart($method, $id, $this->tracer->includeArgs() ? func_get_args() : [])); $toReturn = $this->cache->getManyWithConfig($key, $features, $attributes); $features = self::getMissing($toReturn); @@ -103,7 +134,9 @@ public function getTreatmentsWithConfig(string $key, ?string $bucketingKey, arra return $toReturn; } + $this->tracer->trace(TEF::forRPCStart($method, $id)); $results = $this->lm->getTreatmentsWithConfig($key, $bucketingKey, $features, $attributes); + $this->tracer->trace(TEF::forRPCEnd($method, $id)); foreach ($results as $feature => $result) { list($treatment, $ilData, $config) = $result; $toReturn[$feature] = ['treatment' => $treatment, 'config' => $config]; @@ -112,23 +145,36 @@ public function getTreatmentsWithConfig(string $key, ?string $bucketingKey, arra $this->cache->setManyWithConfig($key, $attributes, $toReturn); return $toReturn; } catch (\Exception $exc) { + $this->tracer->trace(TEF::forException($method, $id, $exc)); $this->logger->error($exc); return array_reduce($features, function ($r, $k) { $r[$k] = ['treatment' => 'control', 'config' => null]; return $r; }, []); + } finally { + $this->tracer->trace(TEF::forEnd($method, $id)); } } public function track(string $key, string $trafficType, string $eventType, ?float $value = null, ?array $properties = null): bool { try { + $id = $this->tracer->makeId(); + $method = Tracer::METHOD_TRACK; + $this->tracer->trace(TEF::forStart($method, $id, $this->tracer->includeArgs() ? func_get_args() : [])); $properties = $this->inputValidator->validProperties($properties); - return $this->lm->track($key, $trafficType, $eventType, $value, $properties); + $this->tracer->trace(TEF::forRPCStart($method, $id)); + $res = $this->lm->track($key, $trafficType, $eventType, $value, $properties); + $this->tracer->trace(TEF::forRPCEnd($method, $id)); + return $res; } catch (ValidationException $exc) { + $this->tracer->trace(TEF::forException($method, $id, $exc)); $this->logger->error("error validating event properties: " . $exc->getMessage()); } catch (\Exception $exc) { + $this->tracer->trace(TEF::forException($method, $id, $exc)); $this->logger->error($exc); + } finally { + $this->tracer->trace(TEF::forEnd($method, $id)); } return false; } diff --git a/src/Config/Tracer.php b/src/Config/Tracer.php new file mode 100644 index 0000000..82bc4e6 --- /dev/null +++ b/src/Config/Tracer.php @@ -0,0 +1,43 @@ +hook = $hook; + $this->forwardArgs = $forwardArguments; + } + + public function hook(): ?TracerHook + { + return $this->hook; + } + + public function forwardArgs(): bool + { + return $this->forwardArgs; + } + + public static function fromArray(array $config): Tracer + { + $d = self::default(); + return new Tracer( + $config['hook'] ?? $d->hook(), + $config['forwardArgs'] ?? $d->forwardArgs, + ); + } + + public static function default(): Tracer + { + return new Tracer(null, false); + } +} diff --git a/src/Config/Utils.php b/src/Config/Utils.php index 2b38b31..89be7e0 100644 --- a/src/Config/Utils.php +++ b/src/Config/Utils.php @@ -9,11 +9,13 @@ class Utils { private /*?ImpressionListener*/ $listener; private /*?string*/ $evaluationCache; + private /*?TracerHook*/ $tracer; - private function __construct(?ImpressionListener $listener, EvaluationCache $cache) + private function __construct(?ImpressionListener $listener, EvaluationCache $cache, Tracer $tracer) { $this->listener = $listener; $this->evaluationCache = $cache; + $this->tracer = $tracer; } public function impressionListener(): ?ImpressionListener @@ -21,6 +23,11 @@ public function impressionListener(): ?ImpressionListener return $this->listener; } + public function tracer(): Tracer + { + return $this->tracer; + } + public function evaluationCache(): ?EvaluationCache { return $this->evaluationCache; @@ -32,11 +39,12 @@ public static function fromArray(array $config): Utils return new Utils( $config['impressionListener'] ?? $d->impressionListener(), EvaluationCache::fromArray($config['evaluationCache'] ?? []), + Tracer::fromArray($config['tracer'] ?? []), ); } public static function default(): Utils { - return new Utils(null, EvaluationCache::default()); + return new Utils(null, EvaluationCache::default(), Tracer::default()); } } diff --git a/src/Factory.php b/src/Factory.php index b821128..aca0fbf 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -4,6 +4,7 @@ use SplitIO\ThinSdk\Foundation\Logging; use SplitIO\ThinSdk\Utils\EvalCache; +use SplitIO\ThinSdk\Utils\Tracing\Tracer; class Factory implements FactoryInterface { @@ -65,7 +66,8 @@ public function client(): ClientInterface $this->linkManager, $this->logger, $uc->impressionListener(), - EvalCache\Helpers::getCache($uc->evaluationCache(), $this->logger) + EvalCache\Helpers::getCache($uc->evaluationCache(), $this->logger), + new Tracer($uc->tracer()->hook(), $uc->tracer()->forwardArgs()), ); } diff --git a/src/Link/Transfer/ConnectionFactory.php b/src/Link/Transfer/ConnectionFactory.php index 2d57b99..0f0f0ea 100644 --- a/src/Link/Transfer/ConnectionFactory.php +++ b/src/Link/Transfer/ConnectionFactory.php @@ -32,16 +32,26 @@ public function create(): RawConnection throw new \Exception("invalid connection type " . $this->sockType); } - private static function formatTimeout(?int $milliseconds)/*: ?int */ + private static function formatTimeout($timeout)/*: ?int */ { - if ($milliseconds == null) { - $milliseconds = 1000; + if (is_array($timeout)) { + // assume it's a properly formatted unix-like timeout (including 'sec' & 'usec') + if (!array_key_exists('sec', $timeout) || !array_key_exists('usec', $timeout)) { + throw new \Exception("timeout must either be an int (milliseconds) or an array with keys 'sec' & 'usec'"); + } + return $timeout; + } + + if (!is_null($timeout) && !is_int($timeout)) { + throw new \Exception("timeout must either be an int (milliseconds) or an array with keys 'sec' & 'usec'"); } + if ($timeout == null || $timeout == 0) { + $timeout = 1000; + } return [ - 'sec' => $milliseconds / 1000, - 'usec' => 0, // TODO(mredolatti): handle seconds fractions in usec units + 'sec' => floor($timeout / 1000), + 'usec' => ($timeout % 1000) * 1000, ]; } - } diff --git a/src/Utils/Tracing/NoOpTracerHook.php b/src/Utils/Tracing/NoOpTracerHook.php new file mode 100644 index 0000000..674bfa6 --- /dev/null +++ b/src/Utils/Tracing/NoOpTracerHook.php @@ -0,0 +1,10 @@ +hook = $hook ?? new NoOpTracerHook(); + $this->includeArgs = $includeArgs; + } + + public function includeArgs(): bool + { + return $this->includeArgs; + } + + public function trace(array $event) + { + $this->hook->on($event); + } + + public function makeId(): string + { + return uniqid("", true); + } +} diff --git a/src/Utils/Tracing/TracerHook.php b/src/Utils/Tracing/TracerHook.php new file mode 100644 index 0000000..405399f --- /dev/null +++ b/src/Utils/Tracing/TracerHook.php @@ -0,0 +1,8 @@ + $id, 'method' => $method, 'event' => Tracer::EVENT_START]; + return is_null($arguments) + ? $base + : array_merge($base, ['arguments' => $arguments]); + } + + public static function forRPCStart(int $method, string $id): array + { + return ['id' => $id, 'method' => $method, 'event' => Tracer::EVENT_RPC_START]; + } + + public static function forRPCEnd(int $method, string $id): array + { + return ['id' => $id, 'method' => $method, 'event' => Tracer::EVENT_RPC_END]; + } + + public static function forException(int $method, string $id, \Exception $exception): array + { + return [ + 'id' => $id, + 'method' => $method, + 'event' => Tracer::EVENT_EXCEPTION, + 'exception' => $exception, + ]; + } + + public static function forEnd(int $method, string $id): array + { + return [ + 'id' => $id, + 'method' => $method, + 'event' => Tracer::EVENT_END, + ]; + } +} diff --git a/src/Version.php b/src/Version.php index c9b6dab..d4ff141 100644 --- a/src/Version.php +++ b/src/Version.php @@ -4,5 +4,5 @@ class Version { - const CURRENT = '1.3.0'; + const CURRENT = '1.4.0'; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 90ce70b..c6e9838 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -4,6 +4,7 @@ use SplitIO\ThinSdk\Client; use SplitIO\ThinSdk\Utils\ImpressionListener; +use SplitIO\ThinSdk\Utils\Tracing\Tracer; use SplitIO\ThinSdk\Utils\EvalCache\CacheImpl; use SplitIO\ThinSdk\Utils\EvalCache\KeyAttributeCRC32Hasher; use SplitIO\ThinSdk\Utils\EvalCache\NoEviction; @@ -31,7 +32,37 @@ public function testGetTreatmentNoImpListener() ->with('someKey', 'someBuck', 'someFeature', ['someAttr' => 123]) ->willReturn(['on', null, null]); - $client = new Client($manager, $this->logger, null); + $tracer = $this->createMock(Tracer::class); + $tracer->expects($this->once())->method('includeArgs')->willReturn(true); + $tracer->expects($this->once())->method('makeId')->willReturn('some_id'); + + $tracer->expects($this->exactly(4)) + ->method('trace') + ->withConsecutive( + [[ + 'id' => 'some_id', + 'method' => Tracer::METHOD_GET_TREATMENT, + 'event' => Tracer::EVENT_START, + 'arguments' => ['someKey', 'someBuck', 'someFeature', ['someAttr' => 123]], + ]], + [[ + 'id' => 'some_id', + 'method' => Tracer::METHOD_GET_TREATMENT, + 'event' => Tracer::EVENT_RPC_START, + ]], + [[ + 'id' => 'some_id', + 'method' => Tracer::METHOD_GET_TREATMENT, + 'event' => Tracer::EVENT_RPC_END, + ]], + [[ + 'id' => 'some_id', + 'method' => Tracer::METHOD_GET_TREATMENT, + 'event' => Tracer::EVENT_END, + ]], + ); + + $client = new Client($manager, $this->logger, null, null, $tracer); $this->assertEquals('on', $client->getTreatment('someKey', 'someBuck', 'someFeature', ['someAttr' => 123])); } @@ -78,6 +109,36 @@ public function testGetTreatmentsWithImpListener() 'someFeature3' => ['n/a', new ImpressionListenerData('lab1', 125, 123458), null], ]); + $tracer = $this->createMock(Tracer::class); + $tracer->expects($this->once())->method('includeArgs')->willReturn(true); + $tracer->expects($this->once())->method('makeId')->willReturn('some_id2'); + $tracer->expects($this->exactly(4)) + ->method('trace') + ->withConsecutive( + [[ + 'id' => 'some_id2', + 'method' => Tracer::METHOD_GET_TREATMENTS, + 'event' => Tracer::EVENT_START, + 'arguments' => ['someKey', 'someBuck', ['someFeature1', 'someFeature2', 'someFeature3'], ['someAttr' => 123]], + ]], + [[ + 'id' => 'some_id2', + 'method' => Tracer::METHOD_GET_TREATMENTS, + 'event' => Tracer::EVENT_RPC_START, + ]], + [[ + 'id' => 'some_id2', + 'method' => Tracer::METHOD_GET_TREATMENTS, + 'event' => Tracer::EVENT_RPC_END, + ]], + [[ + 'id' => 'some_id2', + 'method' => Tracer::METHOD_GET_TREATMENTS, + 'event' => Tracer::EVENT_END, + ]] + ); + + $ilMock = $this->createMock(ImpressionListener::class); $ilMock->expects($this->exactly(3)) ->method('accept') @@ -88,7 +149,7 @@ public function testGetTreatmentsWithImpListener() ); - $client = new Client($manager, $this->logger, $ilMock); + $client = new Client($manager, $this->logger, $ilMock, null, $tracer); $this->assertEquals( ['someFeature1' => 'on', 'someFeature2' => 'off', 'someFeature3' => 'n/a'], $client->getTreatments('someKey', 'someBuck', ['someFeature1', 'someFeature2', 'someFeature3'], ['someAttr' => 123]) @@ -107,7 +168,36 @@ public function testGetTreatmentWithConfigAndListener() ->method('accept') ->with(new Impression('someKey', 'someBuck', 'someFeature', 'on', 'lab1', 123, 123456), ['someAttr' => 123]); - $client = new Client($manager, $this->logger, $ilMock); + $tracer = $this->createMock(Tracer::class); + $tracer->expects($this->once())->method('includeArgs')->willReturn(true); + $tracer->expects($this->once())->method('makeId')->willReturn('some_id3'); + $tracer->expects($this->exactly(4)) + ->method('trace') + ->withConsecutive( + [[ + 'id' => 'some_id3', + 'method' => Tracer::METHOD_GET_TREATMENT_WITH_CONFIG, + 'event' => Tracer::EVENT_START, + 'arguments' => ['someKey', 'someBuck', 'someFeature', ['someAttr' => 123]], + ]], + [[ + 'id' => 'some_id3', + 'method' => Tracer::METHOD_GET_TREATMENT_WITH_CONFIG, + 'event' => Tracer::EVENT_RPC_START, + ]], + [[ + 'id' => 'some_id3', + 'method' => Tracer::METHOD_GET_TREATMENT_WITH_CONFIG, + 'event' => Tracer::EVENT_RPC_END, + ]], + [[ + 'id' => 'some_id3', + 'method' => Tracer::METHOD_GET_TREATMENT_WITH_CONFIG, + 'event' => Tracer::EVENT_END, + ]], + ); + + $client = new Client($manager, $this->logger, $ilMock, null, $tracer); $this->assertEquals( ['treatment' => 'on', 'config' => '{"a": 1}'], $client->getTreatmentWithConfig('someKey', 'someBuck', 'someFeature', ['someAttr' => 123]) @@ -134,8 +224,36 @@ public function testGetTreatmentsWithConfigAndListener() [new Impression('someKey', 'someBuck', 'someFeature3', 'n/a', 'lab1', 125, 123458), ['someAttr' => 123]] ); + $tracer = $this->createMock(Tracer::class); + $tracer->expects($this->once())->method('includeArgs')->willReturn(true); + $tracer->expects($this->once())->method('makeId')->willReturn('some_id4'); + $tracer->expects($this->exactly(4)) + ->method('trace') + ->withConsecutive( + [[ + 'id' => 'some_id4', + 'method' => Tracer::METHOD_GET_TREATMENTS_WITH_CONFIG, + 'event' => Tracer::EVENT_START, + 'arguments' => ['someKey', 'someBuck', ['someFeature1', 'someFeature2', 'someFeature3'], ['someAttr' => 123]], + ]], + [[ + 'id' => 'some_id4', + 'method' => Tracer::METHOD_GET_TREATMENTS_WITH_CONFIG, + 'event' => Tracer::EVENT_RPC_START, + ]], + [[ + 'id' => 'some_id4', + 'method' => Tracer::METHOD_GET_TREATMENTS_WITH_CONFIG, + 'event' => Tracer::EVENT_RPC_END, + ]], + [[ + 'id' => 'some_id4', + 'method' => Tracer::METHOD_GET_TREATMENTS_WITH_CONFIG, + 'event' => Tracer::EVENT_END, + ]] + ); - $client = new Client($manager, $this->logger, $ilMock); + $client = new Client($manager, $this->logger, $ilMock, null, $tracer); $this->assertEquals( [ 'someFeature1' => ['treatment' => 'on', 'config' => null], diff --git a/tests/Config/TraceTest.php b/tests/Config/TraceTest.php new file mode 100644 index 0000000..e187422 --- /dev/null +++ b/tests/Config/TraceTest.php @@ -0,0 +1,31 @@ +assertEquals(null, $cfg->hook()); + $this->assertEquals(false, $cfg->forwardArgs()); + } + + public function testConfigParsing() + { + $tMock = $this->createMock(TracerHook::class); + $cfg = Tracer::fromArray([ + 'hook' => $tMock, + 'forwardArgs' => true, + ]); + + $this->assertEquals($tMock, $cfg->hook()); + $this->assertEquals(true, $cfg->forwardArgs()); + } +} diff --git a/tests/Config/UtilsTest.php b/tests/Config/UtilsTest.php index d42eb6d..674cfd1 100644 --- a/tests/Config/UtilsTest.php +++ b/tests/Config/UtilsTest.php @@ -4,8 +4,10 @@ use SplitIO\ThinSdk\Config\Utils; use SplitIO\ThinSdk\Config\EvaluationCache; +use SplitIO\ThinSdk\Config\Tracer; use SplitIO\ThinSdk\Utils\ImpressionListener; use SplitIO\ThinSdk\Utils\EvalCache\InputHasher; +use SplitIO\ThinSdk\Utils\Tracing\TracerHook; use PHPUnit\Framework\TestCase; @@ -17,12 +19,14 @@ public function testConfigDefault() $cfg = Utils::default(); $this->assertEquals(null, $cfg->impressionListener()); $this->assertEquals(EvaluationCache::default(), $cfg->evaluationCache()); + $this->assertEquals(Tracer::default(), $cfg->tracer()); } public function testConfigParsing() { $ilMock = $this->createMock(ImpressionListener::class); $ihMock = $this->createMock(InputHasher::class); + $tMock = $this->createMock(TracerHook::class); $cfg = Utils::fromArray([ 'impressionListener' => $ilMock, 'evaluationCache' => [ @@ -31,6 +35,10 @@ public function testConfigParsing() 'maxSize' => 1234, 'customHash' => $ihMock, ], + 'tracer' => [ + 'hook' => $tMock, + 'forwardArgs' => true, + ], ]); $this->assertEquals($ilMock, $cfg->impressionListener()); @@ -38,5 +46,7 @@ public function testConfigParsing() $this->assertEquals('random', $cfg->evaluationCache()->evictionPolicy()); $this->assertEquals($ihMock, $cfg->evaluationCache()->customHash()); $this->assertEquals(1234, $cfg->evaluationCache()->maxSize()); + $this->assertEquals($tMock, $cfg->tracer()->hook()); + $this->assertEquals(true, $cfg->tracer()->forwardArgs()); } } diff --git a/tests/Link/Transfer/ConnectionFactoryTest.php b/tests/Link/Transfer/ConnectionFactoryTest.php new file mode 100644 index 0000000..c2c868b --- /dev/null +++ b/tests/Link/Transfer/ConnectionFactoryTest.php @@ -0,0 +1,41 @@ +getMethod('formatTimeout'); + $m->setAccessible(true); + $this->assertEquals(['sec' => 1, 'usec' => 0], $m->invoke(null, ['sec' =>1, 'usec' => 0])); + $this->assertEquals(['sec' => 1, 'usec' => 0], $m->invoke(null, 0)); + $this->assertEquals(['sec' => 1, 'usec' => 0], $m->invoke(null, 1000)); + $this->assertEquals(['sec' => 1, 'usec' => 500000], $m->invoke(null, 1500)); + $this->assertEquals(['sec' => 0, 'usec' => 500000], $m->invoke(null, 500)); + $this->assertEquals(['sec' => 0, 'usec' => 1000], $m->invoke(null, 1)); + } + + public function testInvalidArrayTimeout(): void + { + $c = new \ReflectionClass(ConnectionFactory::class); + $m = $c->getMethod('formatTimeout'); + $m->setAccessible(true); + $this->expectExceptionMessage("timeout must either be an int (milliseconds) or an array with keys 'sec' & 'usec'"); + $m->invoke(null, []); + } + + public function testNonArrayNonIntTimeout(): void + { + $c = new \ReflectionClass(ConnectionFactory::class); + $m = $c->getMethod('formatTimeout'); + $m->setAccessible(true); + $this->expectExceptionMessage("timeout must either be an int (milliseconds) or an array with keys 'sec' & 'usec'"); + $m->invoke(null, 98.7); + } + +} diff --git a/tests/Utils/Tracing/TracingEventFactoryTest.php b/tests/Utils/Tracing/TracingEventFactoryTest.php new file mode 100644 index 0000000..43d0a96 --- /dev/null +++ b/tests/Utils/Tracing/TracingEventFactoryTest.php @@ -0,0 +1,40 @@ +assertEquals( + ['id' => '123', 'method' => Tracer::METHOD_GET_TREATMENT, 'event' => Tracer::EVENT_START, 'arguments' => ['a', 'b', 'c']], + TEF::forStart(Tracer::METHOD_GET_TREATMENT, '123', ['a', 'b', 'c']), + ); + + $this->assertEquals( + ['id' => '123', 'method' => Tracer::METHOD_GET_TREATMENTS, 'event' => Tracer::EVENT_RPC_START], + TEF::forRPCStart(Tracer::METHOD_GET_TREATMENTS, '123'), + ); + + $this->assertEquals( + ['id' => '123', 'method' => Tracer::METHOD_GET_TREATMENT_WITH_CONFIG, 'event' => Tracer::EVENT_RPC_END], + TEF::forRPCEnd(Tracer::METHOD_GET_TREATMENT_WITH_CONFIG, '123'), + ); + + $this->assertEquals( + ['id' => '123', 'method' => Tracer::METHOD_GET_TREATMENTS_WITH_CONFIG, 'event' => Tracer::EVENT_END], + TEF::forEnd(Tracer::METHOD_GET_TREATMENTS_WITH_CONFIG, '123'), + ); + + $this->assertEquals( + ['id' => '123', 'method' => Tracer::METHOD_TRACK, 'event' => Tracer::EVENT_EXCEPTION, 'exception' => new \Exception("sarasa")], + TEF::forException(Tracer::METHOD_TRACK, '123', new \Exception("sarasa")), + ); + } +}