mirror of
https://github.com/e107inc/e107.git
synced 2025-08-07 07:06:30 +02:00
Greatly improved robustness of E107Base
Fixed irreversible data loss bug when preparing the app path repo for tests OLD BEHAVIOR Remove untracked files from the working tree before and after every test suite. The intention was to remove files that could have been created by the tests, but this also removes all other untracked files, including third-party plugins and themes. NEW BEHAVIOR There is now a triple locking mechanism protecting the state of the app repository before tests are run so that after tests have run, all file contents are restored to how they were before the tests have run. There are three locks, each guarding a different part of the original repo: * file * commit * stash When the file lock is present, E107Base knows that any other file, tracked, untracked, or ignored, was created by the tested code. The file lock is checked into the commit lock, which saves all tracked and untracked files in the form of a commit. This way, uncommitted code can be tested without manually making a commit that includes the untracked files. The stash lock saves all ignored files in a Git stash. Ignored files go in a stash instead of in the commit lock because they can be a confounding variable affecting the outcome of tests. They are usually user-configurable only and may not represent tested states. When the saving is complete, E107Base will have access to tracked and untracked files but not ignored files. The tests are run in this configuration. After each test suite runs, the repo is reset and cleaned to return the files to the same state they were in before the test suite began. Then, the locks are undone first by rolling back the commit lock, which restores the originally uncommitted tracked and untracked files. Next, the stash lock is popped, restoring the originally ignored files. Finally, the file lock is removed, signifying that the repo is back to its original data before the test suite was run. In case of an ungraceful exit, at least one of the locks would be left in the repo. The next time E107Base runs, locks are checked, and if one or more are present, the repo is restored before the locks are reestablished. If the `git` command is inoperative, only the file lock is operative and does nothing more than signify a test that is in progress or has exited unexpectedly. Data protection is silently unenforced. This can lead to unexpected tracked, untracked, and ignored file modifications that will not be rolled back after a test suite has run.
This commit is contained in:
2
e107
2
e107
Submodule e107 updated: adce28a1f5...7340ea8377
@@ -6,23 +6,114 @@ namespace Helper;
|
||||
|
||||
abstract class E107Base extends Base
|
||||
{
|
||||
public $e107_mySQLprefix = 'e107_';
|
||||
const TEST_IN_PROGRESS = 'TEST-IN-PROGRESS';
|
||||
const TEST_IN_PROGRESS_FILE = APP_PATH."/".self::TEST_IN_PROGRESS;
|
||||
const APP_PATH_E107_CONFIG = APP_PATH."/e107_config.php";
|
||||
public $e107_mySQLprefix = 'e107_';
|
||||
|
||||
public function _beforeSuite($settings = array())
|
||||
{
|
||||
$this->backupLocalE107Config();
|
||||
$this->cleanVCS();
|
||||
$this->setVcsInProgress();
|
||||
parent::_beforeSuite($settings);
|
||||
$this->writeLocalE107Config();
|
||||
}
|
||||
|
||||
public function _afterSuite()
|
||||
protected function backupLocalE107Config()
|
||||
{
|
||||
parent::_afterSuite();
|
||||
$this->revokeLocalE107Config();
|
||||
$this->cleanVCS();
|
||||
$this->restoreLocalE107Config();
|
||||
if(file_exists(self::APP_PATH_E107_CONFIG))
|
||||
{
|
||||
rename(self::APP_PATH_E107_CONFIG, APP_PATH.'/e107_config.php.bak');
|
||||
}
|
||||
}
|
||||
|
||||
protected function setVcsInProgress()
|
||||
{
|
||||
if ($this->isVcsInProgress())
|
||||
{
|
||||
codecept_debug('Git repo shows test in progress. Probably crashed test.');
|
||||
$this->unsetVcsInProgress();
|
||||
}
|
||||
|
||||
codecept_debug('Setting VCS in progress…');
|
||||
|
||||
touch(self::TEST_IN_PROGRESS_FILE);
|
||||
$this->runCommand('git add -f '.escapeshellarg(self::TEST_IN_PROGRESS_FILE));
|
||||
$this->runCommand('git add -A');
|
||||
|
||||
$commit_command = 'git 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 stash pop',
|
||||
'git reset --mixed HEAD^',
|
||||
'rm -fv '.escapeshellarg(self::TEST_IN_PROGRESS)
|
||||
];
|
||||
foreach($unsetVcsInProgress_commands as $command)
|
||||
{
|
||||
$commit_command .= "-m ".escapeshellarg($command)." ";
|
||||
}
|
||||
$this->runCommand($commit_command);
|
||||
$this->runCommand('git stash push --all -m '.escapeshellarg(self::TEST_IN_PROGRESS));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
proc_close($resource);
|
||||
}
|
||||
|
||||
protected function unsetVcsInProgress()
|
||||
{
|
||||
codecept_debug('Rolling back VCS to pre-test state…');
|
||||
$this->runCommand('git reset --hard HEAD');
|
||||
$this->runCommand('git clean -fdx');
|
||||
|
||||
while ($this->isVcsInProgress('commit'))
|
||||
{
|
||||
codecept_debug('Going back one commit…');
|
||||
$this->runCommand('git reset --mixed HEAD^');
|
||||
}
|
||||
|
||||
while ($this->isVcsInProgress('stash'))
|
||||
{
|
||||
codecept_debug('Popping top of stash…');
|
||||
$this->runCommand('git stash pop');
|
||||
}
|
||||
|
||||
@unlink(self::TEST_IN_PROGRESS_FILE);
|
||||
}
|
||||
|
||||
protected function writeLocalE107Config()
|
||||
@@ -45,44 +136,25 @@ abstract class E107Base extends Base
|
||||
file_put_contents(self::APP_PATH_E107_CONFIG, $e107_config_contents);
|
||||
}
|
||||
|
||||
public function _afterSuite()
|
||||
{
|
||||
parent::_afterSuite();
|
||||
$this->revokeLocalE107Config();
|
||||
$this->unsetVcsInProgress();
|
||||
$this->restoreLocalE107Config();
|
||||
}
|
||||
|
||||
protected function revokeLocalE107Config()
|
||||
{
|
||||
if (file_exists(self::APP_PATH_E107_CONFIG))
|
||||
unlink(self::APP_PATH_E107_CONFIG);
|
||||
}
|
||||
|
||||
protected function cleanVCS()
|
||||
{
|
||||
$descriptorspec = [
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
];
|
||||
$pipes = [];
|
||||
$resource = proc_open('git clean -fdx', $descriptorspec, $pipes, APP_PATH);
|
||||
//$stdout = stream_get_contents($pipes[1]);
|
||||
//$stderr = stream_get_contents($pipes[2]);
|
||||
//foreach ($pipes as $pipe)
|
||||
//{
|
||||
// fclose($pipe);
|
||||
//}
|
||||
//var_dump($stdout);
|
||||
//var_dump($stderr);
|
||||
proc_close($resource);
|
||||
}
|
||||
|
||||
protected function backupLocalE107Config()
|
||||
{
|
||||
if(file_exists(self::APP_PATH_E107_CONFIG))
|
||||
{
|
||||
rename(self::APP_PATH_E107_CONFIG, APP_PATH.'/e107_config.bak');
|
||||
}
|
||||
}
|
||||
|
||||
protected function restoreLocalE107Config()
|
||||
{
|
||||
if(file_exists(APP_PATH."/e107_config.bak"))
|
||||
if(file_exists(APP_PATH."/e107_config.php.bak"))
|
||||
{
|
||||
rename(APP_PATH.'/e107_config.bak', self::APP_PATH_E107_CONFIG);
|
||||
rename(APP_PATH.'/e107_config.php.bak', self::APP_PATH_E107_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user