| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 |
- #!/usr/bin/env php
- <?php
- /**
- * generates a config unit test class
- *
- * This generator is meant to test all possible configuration combinations
- * without having to write endless amounts of code manually.
- *
- * DANGER: Too many options/settings and too high max iteration setting may trigger
- * a fork bomb. Please save your work before executing this script.
- */
- include 'Bootstrap.php';
- $vrd = array('view', 'read', 'delete');
- $vcud = array('view', 'create', 'read', 'delete');
- new ConfigurationTestGenerator(array(
- 'main/discussion' => array(
- array(
- 'setting' => true,
- 'tests' => array(
- array(
- 'conditions' => array('steps' => $vrd),
- 'type' => 'RegExp',
- 'args' => array(
- '#<div[^>]*id="opendisc"[^>]*>#',
- '$content',
- 'outputs enabled discussion correctly',
- ),
- ), array(
- 'conditions' => array('steps' => array('create'), 'traffic/limit' => 10),
- 'settings' => array('$_POST["opendiscussion"] = "neither 1 nor 0"'),
- 'type' => 'Equals',
- 'args' => array(
- 1,
- '$response["status"]',
- 'when discussions are enabled, but invalid flag posted, fail to create paste',
- ),
- ), array(
- 'conditions' => array('steps' => array('create'), 'traffic/limit' => 10),
- 'settings' => array('$_POST["opendiscussion"] = "neither 1 nor 0"'),
- 'type' => 'False',
- 'args' => array(
- '$this->_model->exists(Helper::getPasteId())',
- 'when discussions are enabled, but invalid flag posted, paste is not created',
- ),
- ),
- ),
- 'affects' => $vcud,
- ), array(
- 'setting' => false,
- 'tests' => array(
- array(
- 'type' => 'NotRegExp',
- 'args' => array(
- '#<div[^>]*id="opendisc"[^>]*>#',
- '$content',
- 'outputs disabled discussion correctly',
- ),
- ),
- ),
- 'affects' => $vrd,
- ),
- ),
- 'main/opendiscussion' => array(
- array(
- 'setting' => true,
- 'tests' => array(
- array(
- 'conditions' => array('main/discussion' => true),
- 'type' => 'RegExp',
- 'args' => array(
- '#<input[^>]+id="opendiscussion"[^>]*checked="checked"[^>]*>#',
- '$content',
- 'outputs checked discussion correctly',
- ),
- ),
- ),
- 'affects' => $vrd,
- ), array(
- 'setting' => false,
- 'tests' => array(
- array(
- 'conditions' => array('main/discussion' => true),
- 'type' => 'NotRegExp',
- 'args' => array(
- '#<input[^>]+id="opendiscussion"[^>]*checked="checked"[^>]*>#',
- '$content',
- 'outputs unchecked discussion correctly',
- ),
- ),
- ),
- 'affects' => $vrd,
- ),
- ),
- 'main/burnafterreadingselected' => array(
- array(
- 'setting' => true,
- 'tests' => array(
- array(
- 'type' => 'RegExp',
- 'args' => array(
- '#<input[^>]+id="burnafterreading"[^>]*checked="checked"[^>]*>#',
- '$content',
- 'preselects burn after reading option',
- ),
- ),
- ),
- 'affects' => array('view'),
- ), array(
- 'setting' => false,
- 'tests' => array(
- array(
- 'type' => 'NotRegExp',
- 'args' => array(
- '#<input[^>]+id="burnafterreading"[^>]*checked="checked"[^>]*>#',
- '$content',
- 'burn after reading option is unchecked',
- ),
- ),
- ),
- 'affects' => array('view'),
- ),
- ),
- 'main/password' => array(
- array(
- 'setting' => true,
- 'tests' => array(
- array(
- 'type' => 'RegExp',
- 'args' => array(
- '#<div[^>]*id="password"[^>]*>#',
- '$content',
- 'outputs password input correctly',
- ),
- ),
- ),
- 'affects' => $vrd,
- ), array(
- 'setting' => false,
- 'tests' => array(
- array(
- 'conditions' => array('main/discussion' => true),
- 'type' => 'NotRegExp',
- 'args' => array(
- '#<div[^>]*id="password"[^>]*>#',
- '$content',
- 'removes password input correctly',
- ),
- ),
- ),
- 'affects' => $vrd,
- ),
- ),
- 'main/template' => array(
- array(
- 'setting' => 'page',
- 'tests' => array(
- array(
- 'type' => 'RegExp',
- 'args' => array(
- '#<link[^>]+type="text/css"[^>]+rel="stylesheet"[^>]+href="css/privatebin\.css\\?\d+\.\d+"[^>]*/>#',
- '$content',
- 'outputs "page" stylesheet correctly',
- ),
- ), array(
- 'type' => 'NotRegExp',
- 'args' => array(
- '#<link[^>]+type="text/css"[^>]+rel="stylesheet"[^>]+href="css/bootstrap/bootstrap-\d[\d\.]+\d\.css"[^>]*/>#',
- '$content',
- 'removes "bootstrap" stylesheet correctly',
- ),
- ),
- ),
- 'affects' => $vrd,
- ), array(
- 'setting' => 'bootstrap',
- 'tests' => array(
- array(
- 'type' => 'NotRegExp',
- 'args' => array(
- '#<link[^>]+type="text/css"[^>]+rel="stylesheet"[^>]+href="css/privatebin\.css\\?\d+\.\d+"[^>]*/>#',
- '$content',
- 'removes "page" stylesheet correctly',
- ),
- ), array(
- 'type' => 'RegExp',
- 'args' => array(
- '#<link[^>]+type="text/css"[^>]+rel="stylesheet"[^>]+href="css/bootstrap/bootstrap-\d[\d\.]+\d\.css"[^>]*/>#',
- '$content',
- 'outputs "bootstrap" stylesheet correctly',
- ),
- ),
- ),
- 'affects' => $vrd,
- ),
- ),
- 'main/sizelimit' => array(
- array(
- 'setting' => 10,
- 'tests' => array(
- array(
- 'conditions' => array('steps' => array('create'), 'traffic/limit' => 10),
- 'type' => 'Equals',
- 'args' => array(
- 1,
- '$response["status"]',
- 'when sizelimit limit exceeded, fail to create paste',
- ),
- ),
- ),
- 'affects' => array('create'),
- ), array(
- 'setting' => 2097152,
- 'tests' => array(
- array(
- 'conditions' => array('steps' => array('create'), 'traffic/limit' => 0, 'main/burnafterreadingselected' => true),
- 'settings' => array('sleep(3)'),
- 'type' => 'Equals',
- 'args' => array(
- 0,
- '$response["status"]',
- 'when sizelimit limit is not reached, successfully create paste',
- ),
- ), array(
- 'conditions' => array('steps' => array('create'), 'traffic/limit' => 0, 'main/burnafterreadingselected' => true),
- 'settings' => array('sleep(3)'),
- 'type' => 'True',
- 'args' => array(
- '$this->_model->exists($response["id"])',
- 'when sizelimit limit is not reached, paste exists after posting data',
- ),
- ),
- ),
- 'affects' => array('create'),
- ),
- ),
- 'traffic/limit' => array(
- array(
- 'setting' => 0,
- 'tests' => array(
- array(
- 'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
- 'type' => 'Equals',
- 'args' => array(
- 0,
- '$response["status"]',
- 'when traffic limit is disabled, successfully create paste',
- ),
- ), array(
- 'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
- 'type' => 'True',
- 'args' => array(
- '$this->_model->exists($response["id"])',
- 'when traffic limit is disabled, paste exists after posting data',
- ),
- ),
- ),
- 'affects' => array('create'),
- ), array(
- 'setting' => 10,
- 'tests' => array(
- array(
- 'conditions' => array('steps' => array('create')),
- 'type' => 'Equals',
- 'args' => array(
- 1,
- '$response["status"]',
- 'when traffic limit is on and we do not wait, fail to create paste',
- ),
- ),
- ),
- 'affects' => array('create'),
- ), array(
- 'setting' => 2,
- 'tests' => array(
- array(
- 'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
- 'settings' => array('sleep(3)'),
- 'type' => 'Equals',
- 'args' => array(
- 0,
- '$response["status"]',
- 'when traffic limit is on and we wait, successfully create paste',
- ),
- ), array(
- 'conditions' => array('steps' => array('create'), 'main/sizelimit' => 2097152),
- 'settings' => array('sleep(3)'),
- 'type' => 'True',
- 'args' => array(
- '$this->_model->exists($response["id"])',
- 'when traffic limit is on and we wait, paste exists after posting data',
- ),
- ),
- ),
- 'affects' => array('create'),
- ),
- ),
- ));
- class ConfigurationTestGenerator
- {
- /**
- * endless loop protection, since we're working with a recursive function,
- * creating factorial configurations
- * @var int
- */
- const MAX_ITERATIONS = 2000;
- /**
- * options to test
- * @var array
- */
- private $_options;
- /**
- * iteration count to guarantee timely end
- * @var int
- */
- private $_iterationCount = 0;
- /**
- * generated configurations
- * @var array
- */
- private $_configurations = array(
- array('options' => array(), 'tests' => array(), 'affects' => array()),
- );
- /**
- * constructor, generates the configuration test
- * @param array $options
- */
- public function __construct($options)
- {
- $this->_options = $options;
- // generate all possible combinations of options: options^settings
- $this->_generateConfigurations();
- $this->_writeConfigurationTest();
- }
- /**
- * write configuration test file based on generated configuration array
- */
- private function _writeConfigurationTest()
- {
- $defaultOptions = parse_ini_file(CONF, true);
- $code = $this->_getHeader();
- foreach ($this->_configurations as $key => $conf) {
- $fullOptions = array_replace_recursive($defaultOptions, $conf['options']);
- $options = Helper::varExportMin($fullOptions, true);
- foreach ($conf['affects'] as $step) {
- $testCode = $preCode = array();
- foreach ($conf['tests'] as $tests) {
- foreach ($tests[0] as $test) {
- // skip if test does not affect this step
- if (!in_array($step, $tests[1])) {
- continue;
- }
- // skip if not all test conditions are met
- if (array_key_exists('conditions', $test)) {
- while (list($path, $setting) = each($test['conditions'])) {
- if ($path == 'steps' && !in_array($step, $setting)) {
- continue 2;
- } elseif ($path != 'steps') {
- list($section, $option) = explode('/', $path);
- if ($fullOptions[$section][$option] !== $setting) {
- continue 2;
- }
- }
- }
- }
- if (array_key_exists('settings', $test)) {
- foreach ($test['settings'] as $setting) {
- $preCode[$setting] = $setting;
- }
- }
- $args = array();
- foreach ($test['args'] as $arg) {
- if (is_string($arg) && strpos($arg, '$') === 0) {
- $args[] = $arg;
- } else {
- $args[] = Helper::varExportMin($arg, true);
- }
- }
- $testCode[] = array($test['type'], implode(', ', $args));
- }
- }
- $code .= $this->_getFunction(
- ucfirst($step), $key, $options, $preCode, $testCode
- );
- }
- }
- $code .= '}' . PHP_EOL;
- file_put_contents('ConfigurationCombinationsTest.php', $code);
- }
- /**
- * get header of configuration test file
- *
- * @return string
- */
- private function _getHeader()
- {
- return <<<'EOT'
- <?php
- /**
- * DO NOT EDIT: This file is generated automatically using configGenerator.php
- */
- use PrivateBin\PrivateBin;
- use PrivateBin\Data\Filesystem;
- use PrivateBin\Persistence\ServerSalt;
- use PrivateBin\Persistence\TrafficLimiter;
- class ConfigurationCombinationsTest extends PHPUnit_Framework_TestCase
- {
- private $_conf;
- private $_model;
- private $_path;
- public function setUp()
- {
- /* Setup Routine */
- Helper::confBackup();
- $this->_path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'privatebin_data';
- $this->_model = Filesystem::getInstance(array('dir' => $this->_path));
- ServerSalt::setPath($this->_path);
- TrafficLimiter::setPath($this->_path);
- $this->reset();
- }
- public function tearDown()
- {
- /* Tear Down Routine */
- Helper::confRestore();
- Helper::rmDir($this->_path);
- }
- public function reset($configuration = array())
- {
- $_POST = array();
- $_GET = array();
- $_SERVER = array();
- if ($this->_model->exists(Helper::getPasteId()))
- $this->_model->delete(Helper::getPasteId());
- $configuration['model_options']['dir'] = $this->_path;
- $configuration['traffic']['dir'] = $this->_path;
- $configuration['purge']['dir'] = $this->_path;
- Helper::createIniFile(CONF, $configuration);
- }
- EOT;
- }
- /**
- * get unit tests function block
- *
- * @param string $step
- * @param int $key
- * @param array $options
- * @param array $preCode
- * @param array $testCode
- * @return string
- */
- private function _getFunction($step, $key, &$options, $preCode, $testCode)
- {
- if (count($testCode) == 0) {
- echo "skipping creation of test$step$key, no valid tests found for configuration: $options". PHP_EOL;
- return '';
- }
- $preString = $testString = '';
- foreach ($preCode as $setting) {
- $preString .= " $setting;" . PHP_EOL;
- }
- foreach ($testCode as $test) {
- $type = $test[0];
- $args = $test[1];
- $testString .= " \$this->assert$type($args);" . PHP_EOL;
- }
- $code = <<<EOT
- /**
- * @runInSeparateProcess
- */
- public function test$step$key()
- {
- \$this->reset($options);
- EOT;
- // step specific initialization
- switch ($step) {
- case 'Create':
- $code .= PHP_EOL . <<<'EOT'
- $_POST = Helper::getPaste();
- $_SERVER['HTTP_X_REQUESTED_WITH'] = 'JSONHttpRequest';
- $_SERVER['REQUEST_METHOD'] = 'POST';
- $_SERVER['REMOTE_ADDR'] = '::1';
- TrafficLimiter::canPass();
- EOT;
- break;
- case 'Read':
- $code .= PHP_EOL . <<<'EOT'
- $this->_model->create(Helper::getPasteId(), Helper::getPaste());
- $_SERVER['QUERY_STRING'] = Helper::getPasteId();
- EOT;
- break;
- case 'Delete':
- $code .= PHP_EOL . <<<'EOT'
- $this->_model->create(Helper::getPasteId(), Helper::getPaste());
- $this->assertTrue($this->_model->exists(Helper::getPasteId()), 'paste exists before deleting data');
- $_GET['pasteid'] = Helper::getPasteId();
- $_GET['deletetoken'] = hash_hmac('sha256', Helper::getPasteId(), $this->_model->read(Helper::getPasteId())->meta->salt);
- EOT;
- break;
- }
- // all steps
- $code .= PHP_EOL . $preString;
- $code .= <<<'EOT'
- ob_start();
- new PrivateBin;
- $content = ob_get_contents();
- ob_end_clean();
- EOT;
- // step specific tests
- switch ($step) {
- case 'Create':
- $code .= <<<'EOT'
- $response = json_decode($content, true);
- EOT;
- break;
- case 'Read':
- $code .= <<<'EOT'
- $this->assertContains(
- htmlspecialchars(json_encode(Helper::getPaste()['data']), ENT_NOQUOTES),
- $content,
- 'outputs data correctly'
- );
- EOT;
- break;
- case 'Delete':
- $code .= <<<'EOT'
- $this->assertRegExp(
- '#<div[^>]*id="status"[^>]*>.*Paste was properly deleted[^<]*</div>#s',
- $content,
- 'outputs deleted status correctly'
- );
- $this->assertFalse($this->_model->exists(Helper::getPasteId()), 'paste successfully deleted');
- EOT;
- break;
- }
- return $code . PHP_EOL . PHP_EOL . $testString . ' }' . PHP_EOL . PHP_EOL;
- }
- /**
- * recursive function to generate configurations based on options
- *
- * @throws Exception
- * @return array
- */
- private function _generateConfigurations()
- {
- // recursive factorial function
- if (++$this->_iterationCount > self::MAX_ITERATIONS) {
- echo 'max iterations reached, stopping', PHP_EOL;
- return $this->_configurations;
- }
- echo "generateConfigurations: iteration $this->_iterationCount", PHP_EOL;
- $continue = list($path, $settings) = each($this->_options);
- if ($continue === false) {
- return $this->_configurations;
- }
- list($section, $option) = explode('/', $path);
- for ($c = 0, $max = count($this->_configurations); $c < $max; ++$c) {
- if (!array_key_exists($section, $this->_configurations[$c]['options'])) {
- $this->_configurations[$c]['options'][$section] = array();
- }
- if (count($settings) == 0) {
- throw new Exception("Check your \$options: option $option has no settings!");
- }
- // set the first setting in the original configuration
- $setting = current($settings);
- $this->_addSetting($this->_configurations[$c], $setting, $section, $option);
- // create clones for each of the other settings
- while ($setting = next($settings)) {
- $clone = $this->_configurations[$c];
- $this->_configurations[] = $this->_addSetting($clone, $setting, $section, $option);
- }
- reset($settings);
- }
- return $this->_generateConfigurations();
- }
- /**
- * add a setting to the given configuration
- *
- * @param array $configuration
- * @param array $setting
- * @param string $section
- * @param string $option
- * @throws Exception
- * @return array
- */
- private function _addSetting(&$configuration, &$setting, &$section, &$option)
- {
- if (++$this->_iterationCount > self::MAX_ITERATIONS) {
- echo 'max iterations reached, stopping', PHP_EOL;
- return $configuration;
- }
- echo "addSetting: iteration $this->_iterationCount", PHP_EOL;
- if (
- array_key_exists($option, $configuration['options'][$section]) &&
- $configuration['options'][$section][$option] === $setting['setting']
- ) {
- $val = Helper::varExportMin($setting['setting'], true);
- throw new Exception("Endless loop or error in options detected: option '$option' already exists with setting '$val' in one of the configurations!");
- }
- $configuration['options'][$section][$option] = $setting['setting'];
- $configuration['tests'][$option] = array($setting['tests'], $setting['affects']);
- foreach ($setting['affects'] as $affects) {
- if (!in_array($affects, $configuration['affects'])) {
- $configuration['affects'][] = $affects;
- }
- }
- return $configuration;
- }
- }
|