e107inc/e107

View on GitHub
e107_tests/lib/preparers/GitPreparer.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

class GitPreparer implements Preparer
{
    const TEST_IN_PROGRESS = 'TEST-IN-PROGRESS';
    const TEST_IN_PROGRESS_FILE = APP_PATH."/".self::TEST_IN_PROGRESS;

    public function snapshot()
    {
        $this->debug('Snapshot requested');
        return $this->setVcsInProgress();
    }

    public function rollback()
    {
        $this->debug('Rollback requested');
        return $this->unsetVcsInProgress();
    }

    protected function setVcsInProgress()
    {
        // Cleanup in case of a fatal error
        PriorityCallbacks::instance()->register_shutdown_function([$this, 'rollback']);

        if ($this->isVcsInProgress())
        {
            $this->debug('Git repo shows test in progress. Probably crashed test.');
            $this->unsetVcsInProgress();
        }

        $this->debug('Setting test locks in Git…');

        touch(self::TEST_IN_PROGRESS_FILE);
        $this->runCommand('git add -f '.escapeshellarg(self::TEST_IN_PROGRESS_FILE));
        $this->runCommand('git add -A -f');

        $commit_command = 'git -c user.name="Test Run" -c user.email="testrun@example.com" commit -a --no-gpg-sign ' .
            "-m '".self::TEST_IN_PROGRESS."! If test crashed, run `git log -1` for instructions' " .
            "-m 'Running the test again after fixing the crash will clear this commit\nand any related stashes.' " .
            "-m 'Alternatively, run these commands to restore the repository to its\npre-test state:' ";
        $unsetVcsInProgress_commands = [
            'git reset --hard HEAD',
            'git clean -fdx',
            'git reset --mixed HEAD^',
            'rm -fv '.escapeshellarg(self::TEST_IN_PROGRESS)
        ];
        foreach($unsetVcsInProgress_commands as $command)
        {
            $commit_command .= "-m ".escapeshellarg($command)." ";
        }

        $stdout = '';
        $stderr = '';
        $rc = $this->runCommand($commit_command, $stdout, $stderr);
        if ($rc !== 0)
        {
            @unlink(self::TEST_IN_PROGRESS_FILE);
            $this->debug('Error taking snapshot with Git!');
            $this->debug('========== STDOUT ==========');
            $this->debug($stdout);
            $this->debug('========== STDERR ==========');
            $this->debug($stderr);
            throw new Exception("Error taking snapshot with Git!");
        }
    }

    protected function isVcsInProgress($case = '')
    {
        $in_progress = [];

        $in_progress['file'] = file_exists(self::TEST_IN_PROGRESS_FILE);

        $stdout = '';
        $this->runCommand('git log -1 --pretty=%B', $stdout);
        $in_progress['commit'] = strpos($stdout, self::TEST_IN_PROGRESS) !== false;

        $stdout = '';
        $this->runCommand('git stash list', $stdout);
        $in_progress['stash'] = strpos($stdout, self::TEST_IN_PROGRESS) !== false;

        if(!empty($case)) return $in_progress[$case];
        return in_array(true, $in_progress);
    }

    /**
     * @param string $command The command to run
     * @param string $stdout Reference to the STDOUT output as a string
     * @param string $stderr Reference to the STDERR output as a string
     * @return int Return code of the command that was run
     */
    protected function runCommand($command, &$stdout = "", &$stderr = "")
    {
        $descriptorspec = [
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w'],
        ];
        $pipes = [];
        $resource = proc_open($command, $descriptorspec, $pipes, APP_PATH);
        $stdout .= stream_get_contents($pipes[1]);
        $stderr .= stream_get_contents($pipes[2]);
        foreach ($pipes as $pipe)
        {
            fclose($pipe);
        }
        return proc_close($resource);
    }

    protected function unsetVcsInProgress()
    {
        if (!$this->isVcsInProgress())
        {
            $this->debug('No test locks found');
            return;
        }

        $this->debug('Rolling back Git repo to pre-test state…');
        $this->runCommand('git reset --hard HEAD');
        $this->runCommand('git clean -fdx');

        while ($this->isVcsInProgress('commit'))
        {
            $this->debug('Going back one commit…');
            $this->runCommand('git reset --mixed HEAD^');
        }

        while ($this->isVcsInProgress('stash'))
        {
            $this->debug('Popping top of stash…');
            $this->runCommand('git stash pop');
        }

        @unlink(self::TEST_IN_PROGRESS_FILE);
    }

    protected function debug($message)
    {
        codecept_debug(__CLASS__ . ': ' . $message);
    }
}