class ServiceManagerTest extends \PHPUnit_Framework_TestCase
{
    // Creates a new ServiceLog that can be used for the purposes of testing. The implementation
    // writes all received callbacks, in order, to the |$log| member part of the instance. The
    // |$runtime| of the callbacks will be ignored as it would make the tests non-deterministic.
    private function createServiceLog() : ServiceLog
    {
        // @codingStandardsIgnoreStart
        // CodeSniffer does not yet understand formatting of anonymous classes.
        return new class implements ServiceLog
        {
            public $finished = 0;
            public $log = [];
            public function onFinish()
            {
                $this->finished++;
            }
            public function onServiceExecuted(string $identifier, float $runtime)
            {
                $this->log[] = ['executed', $identifier];
            }
            public function onServiceException(string $identifier, float $runtime, $exception)
            {
                $this->log[] = ['exception', $identifier, $exception->getMessage()];
            }
        };
        // @codingStandardsIgnoreEnd
    }
    // Verifies that the service manager's state file exists and is writable by the user that's
    // executing the tests. Without these properties, the service manager cannot function.
    public function testStateFileShouldBeWritable()
    {
        $this->assertTrue(file_exists(ServiceManager::STATE_FILE));
        $this->assertTrue(is_writable(ServiceManager::STATE_FILE));
    }
    // Verifies that the state file can be loaded and saved in-place. This function will not
    // modify the state in order for it to be safe to re-run tests on installations.
    public function testStateFileLoadAndSave()
    {
        $serviceManager = new ServiceManager($this->createServiceLog());
        $this->assertTrue($serviceManager->loadState());
        $this->assertTrue($serviceManager->saveState());
    }
    // Verifies that services will be executed in accordance with their frequences. Three services
    // will be faked, each with a different frequency, after which the service manager will be ran
    // over the course of a fake three hours.
    public function testServiceFrequencies()
    {
        $serviceFactory = function (int $frequencyMinutes) {
            // @codingStandardsIgnoreStart
            // CodeSniffer does not yet understand formatting of anonymous classes.
            return new class($frequencyMinutes) implements Service
            {
                public $counter = 0;
                public $frequencyMinutes;
                public function __construct(int $frequencyMinutes)
                {
                    $this->frequencyMinutes = $frequencyMinutes;
                }
                public function getIdentifier() : string
                {
                    return 'timed-service-' . $this->frequencyMinutes;
                }
                public function getFrequencyMinutes() : int
                {
                    return $this->frequencyMinutes;
                }
                public function execute() : bool
                {
                    $this->counter++;
                    return true;
                }
            };
            // @codingStandardsIgnoreEnd
        };
        $serviceLog = $this->createServiceLog();
        $serviceManager = new ServiceManager($serviceLog);
        // Create three services, respectively running every 1, 15 and 60 minutes.
        $minuteService = $serviceFactory(1);
        $quarterlyService = $serviceFactory(15);
        $hourlyService = $serviceFactory(60);
        // Register the services with the Service Manager.
        $serviceManager->registerService($minuteService);
        $serviceManager->registerService($quarterlyService);
        $serviceManager->registerService($hourlyService);
        $currentTimestamp = time();
        // Immitate the time passing for three hours by adding to |$currentTimestamp|.
        for ($minute = 0; $minute < 3 * 60; ++$minute) {
            $serviceManager->execute($currentTimestamp + $minute * 60);
        }
        // Verify that the services' respective counters are set to the expected values.
        $this->assertEquals(180, $minuteService->counter);
        $this->assertEquals(12, $quarterlyService->counter);
        $this->assertEquals(3, $hourlyService->counter);
        // Verify that an equal number of log entries have been written to the log.
        $this->assertEquals(195, count($serviceLog->log));
    }
    // Verifies that entries will be written to the service log as expected, and generate either
    // `executed` or `exception` messages depending on a service's result.
    public function testServiceLog()
    {
        $serviceFactory = function (string $identifier, callable $callback) {
            // @codingStandardsIgnoreStart
            // CodeSniffer does not yet understand formatting of anonymous classes.
            return new class($identifier, $callback) implements Service
            {
                public $callback;
                public $identifier;
                public function __construct(string $identifier, callable $callback)
                {
                    $this->identifier = $identifier;
                    $this->callback = $callback;
                }
                public function getIdentifier() : string
                {
                    return $this->identifier;
                }
                public function getFrequencyMinutes() : int
                {
                    return 1;
                }
                public function execute()
                {
                    return ($this->callback)();
                }
            };
            // @codingStandardsIgnoreEnd
        };
        $serviceLog = $this->createServiceLog();
        $serviceManager = new ServiceManager($serviceLog);
        // Register three services in order: one that succeeds and one that throws.
        $serviceManager->registerService($serviceFactory('id-succeeds', function () {
            return true;
        }));
        $serviceManager->registerService($serviceFactory('id-throws', function () {
            $result = 42 / 0;
            return true;
        }));
        // Execute the service manager. All services will be executed immediately.
        $serviceManager->execute();
        // Verify that the data in the service log is what we expect it to be.
        $this->assertEquals(2, count($serviceLog->log));
        $this->assertEquals([['executed', 'id-succeeds'], ['exception', 'id-throws', 'Division by zero']], $serviceLog->log);
        $this->assertEquals(1, $serviceLog->finished);
    }
}