configGenerator.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. #!/usr/bin/env php
  2. <?php
  3. /**
  4. * generates a config unit test class
  5. *
  6. * This generator is meant to test all possible configuration combinations
  7. * without having to write endless amounts of code manually.
  8. *
  9. * DANGER: Too many options/settings and too high max iteration setting may trigger
  10. * a fork bomb. Please save your work before executing this script.
  11. */
  12. include 'bootstrap.php';
  13. new configurationTestGenerator(array(
  14. 'main/opendiscussion' => array(
  15. array(
  16. 'setting' => true,
  17. 'tests' => array(
  18. array(
  19. 'type' => 'NotTag',
  20. 'args' => array(
  21. array(
  22. 'id' => 'opendiscussion',
  23. 'attributes' => array(
  24. 'disabled' => 'disabled',
  25. ),
  26. ),
  27. '$content',
  28. 'outputs enabled discussion correctly'
  29. ),
  30. ),
  31. ),
  32. 'affects' => array('view')
  33. ), array(
  34. 'setting' => false,
  35. 'tests' => array(
  36. array(
  37. 'type' => 'Tag',
  38. 'args' => array(
  39. array(
  40. 'id' => 'opendiscussion',
  41. 'attributes' => array(
  42. 'disabled' => 'disabled',
  43. ),
  44. ),
  45. '$content',
  46. 'outputs disabled discussion correctly'
  47. ),
  48. ),
  49. ),
  50. 'affects' => array('view')
  51. ),
  52. ),
  53. 'main/syntaxhighlighting' => array(
  54. array(
  55. 'setting' => true,
  56. 'tests' => array(
  57. array(
  58. 'type' => 'Tag',
  59. 'args' => array(
  60. array(
  61. 'tag' => 'link',
  62. 'attributes' => array(
  63. 'type' => 'text/css',
  64. 'rel' => 'stylesheet',
  65. 'href' => 'regexp:#css/prettify/prettify.css#',
  66. ),
  67. ),
  68. '$content',
  69. 'outputs prettify stylesheet correctly',
  70. ),
  71. ), array(
  72. 'type' => 'Tag',
  73. 'args' => array(
  74. array(
  75. 'tag' => 'script',
  76. 'attributes' => array(
  77. 'type' => 'text/javascript',
  78. 'src' => 'regexp:#js/prettify.js#'
  79. ),
  80. ),
  81. '$content',
  82. 'outputs prettify javascript correctly',
  83. ),
  84. ),
  85. ),
  86. 'affects' => array('view'),
  87. ), array(
  88. 'setting' => false,
  89. 'tests' => array(
  90. array(
  91. 'type' => 'NotTag',
  92. 'args' => array(
  93. array(
  94. 'tag' => 'link',
  95. 'attributes' => array(
  96. 'type' => 'text/css',
  97. 'rel' => 'stylesheet',
  98. 'href' => 'regexp:#css/prettify/prettify.css#',
  99. ),
  100. ),
  101. '$content',
  102. 'removes prettify stylesheet correctly',
  103. ),
  104. ), array(
  105. 'type' => 'NotTag',
  106. 'args' => array(
  107. array(
  108. 'tag' => 'script',
  109. 'attributes' => array(
  110. 'type' => 'text/javascript',
  111. 'src' => 'regexp:#js/prettify.js#',
  112. ),
  113. ),
  114. '$content',
  115. 'removes prettify javascript correctly',
  116. ),
  117. ),
  118. ),
  119. 'affects' => array('view'),
  120. ),
  121. ),
  122. 'main/syntaxhighlightingtheme' => array(
  123. array(
  124. 'setting' => 'sons-of-obsidian',
  125. 'tests' => array(
  126. array(
  127. 'conditions' => array('main/syntaxhighlighting' => true),
  128. 'type' => 'Tag',
  129. 'args' => array(
  130. array(
  131. 'tag' => 'link',
  132. 'attributes' => array(
  133. 'type' => 'text/css',
  134. 'rel' => 'stylesheet',
  135. 'href' => 'regexp:#css/prettify/sons-of-obsidian.css#',
  136. ),
  137. ),
  138. '$content',
  139. 'outputs prettify theme stylesheet correctly',
  140. ),
  141. ),
  142. array(
  143. 'conditions' => array('main/syntaxhighlighting' => false),
  144. 'type' => 'NotTag',
  145. 'args' => array(
  146. array(
  147. 'tag' => 'link',
  148. 'attributes' => array(
  149. 'type' => 'text/css',
  150. 'rel' => 'stylesheet',
  151. 'href' => 'regexp:#css/prettify/sons-of-obsidian.css#',
  152. ),
  153. ),
  154. '$content',
  155. 'removes prettify theme stylesheet correctly',
  156. ),
  157. ),
  158. ),
  159. 'affects' => array('view'),
  160. ), array(
  161. 'setting' => null, // option not set
  162. 'tests' => array(
  163. array(
  164. 'type' => 'NotTag',
  165. 'args' => array(
  166. array(
  167. 'tag' => 'link',
  168. 'attributes' => array(
  169. 'type' => 'text/css',
  170. 'rel' => 'stylesheet',
  171. 'href' => 'regexp:#css/prettify/sons-of-obsidian.css#',
  172. ),
  173. ),
  174. '$content',
  175. 'removes prettify theme stylesheet correctly',
  176. ),
  177. ),
  178. ),
  179. 'affects' => array('view'),
  180. ),
  181. ),
  182. ));
  183. class configurationTestGenerator
  184. {
  185. /**
  186. * endless loop protection, since we're working with a recursive function,
  187. * creating factorial configurations
  188. * @var int
  189. */
  190. const MAX_ITERATIONS = 1000;
  191. /**
  192. * options to test
  193. * @var array
  194. */
  195. private $_options;
  196. /**
  197. * iteration count to guarantee timely end
  198. * @var int
  199. */
  200. private $_iterationCount = 0;
  201. /**
  202. * generated configurations
  203. * @var array
  204. */
  205. private $_configurations = array(
  206. array('options' => array(), 'tests' => array(), 'affects' => array())
  207. );
  208. /**
  209. * constructor, generates the configuration test
  210. * @param array $options
  211. */
  212. public function __construct($options) {
  213. $this->_options = $options;
  214. // generate all possible combinations of options: options^settings
  215. $this->_generateConfigurations();
  216. $this->_writeConfigurationTest();
  217. }
  218. /**
  219. * write configuration test file based on generated configuration array
  220. */
  221. private function _writeConfigurationTest()
  222. {
  223. $defaultOptions = parse_ini_file(PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini', true);
  224. $code = $this->_getHeader();
  225. foreach ($this->_configurations as $key => $conf) {
  226. $fullOptions = array_replace_recursive($defaultOptions, $conf['options']);
  227. $options = helper::var_export_min($fullOptions, true);
  228. foreach ($conf['affects'] as $step) {
  229. $step = ucfirst($step);
  230. switch ($step) {
  231. case 'Create':
  232. $code .= <<<EOT
  233. /**
  234. * @runInSeparateProcess
  235. */
  236. public function test$step$key()
  237. {
  238. \$this->reset($options);
  239. \$_POST = self::\$paste;
  240. \$_SERVER['REMOTE_ADDR'] = '::1';
  241. ob_start();
  242. new zerobin;
  243. \$content = ob_get_contents();
  244. \$response = json_decode(\$content, true);
  245. \$this->assertEquals(\$response['status'], 0, 'outputs status');
  246. \$this->assertEquals(
  247. \$response['deletetoken'],
  248. hash_hmac('sha1', \$response['id'], serversalt::get()),
  249. 'outputs valid delete token'
  250. );
  251. \$this->assertTrue(\$this->_model->exists(\$response['id']), 'paste exists after posting data');
  252. EOT;
  253. break;
  254. case 'Read':
  255. $code .= <<<EOT
  256. /**
  257. * @runInSeparateProcess
  258. */
  259. public function test$step$key()
  260. {
  261. \$this->reset($options);
  262. \$this->_model->create(self::\$pasteid, self::\$paste);
  263. \$_SERVER['QUERY_STRING'] = self::\$pasteid;
  264. ob_start();
  265. new zerobin;
  266. \$content = ob_get_contents();
  267. \$this->assertTag(
  268. array(
  269. 'id' => 'cipherdata',
  270. 'content' => htmlspecialchars(json_encode(self::\$paste), ENT_NOQUOTES)
  271. ),
  272. \$content,
  273. 'outputs data correctly'
  274. );
  275. EOT;
  276. break;
  277. case 'Delete':
  278. $code .= <<<EOT
  279. /**
  280. * @runInSeparateProcess
  281. */
  282. public function test$step$key()
  283. {
  284. \$this->reset($options);
  285. \$this->_model->create(self::$\pasteid, self::$\paste);
  286. \$this->assertTrue(\$this->_model->exists(self::$\pasteid), 'paste exists before deleting data');
  287. \$_GET['pasteid'] = self::$\pasteid;
  288. \$_GET['deletetoken'] = hash_hmac('sha1', self::$\pasteid, serversalt::get());
  289. ob_start();
  290. new zerobin;
  291. \$content = ob_get_contents();
  292. \$this->assertTag(
  293. array(
  294. 'id' => 'status',
  295. 'content' => 'Paste was properly deleted'
  296. ),
  297. \$content,
  298. 'outputs deleted status correctly'
  299. );
  300. \$this->assertFalse(\$this->_model->exists(self::\$pasteid), 'paste successfully deleted');
  301. EOT;
  302. break;
  303. // view
  304. default:
  305. $code .= <<<EOT
  306. /**
  307. * @runInSeparateProcess
  308. */
  309. public function test$step$key()
  310. {
  311. \$this->reset($options);
  312. ob_start();
  313. new zerobin;
  314. \$content = ob_get_contents();
  315. EOT;
  316. }
  317. foreach ($conf['tests'] as $tests) {
  318. foreach ($tests as $test) {
  319. if (array_key_exists('conditions', $test)) {
  320. while(list($path, $setting) = each($test['conditions'])) {
  321. list($section, $option) = explode('/', $path);
  322. if ($fullOptions[$section][$option] !== $setting) {
  323. continue 2;
  324. }
  325. }
  326. }
  327. $args = array();
  328. foreach ($test['args'] as $arg) {
  329. if (is_string($arg) && strpos($arg, '$') === 0) {
  330. $args[] = $arg;
  331. } else {
  332. $args[] = helper::var_export_min($arg, true);
  333. }
  334. }
  335. $type = $test['type'];
  336. $args = implode(', ', $args);
  337. $code .= " \$this->assert$type($args);" . PHP_EOL;
  338. }
  339. }
  340. $code .= ' }' . PHP_EOL . PHP_EOL;
  341. }
  342. }
  343. $code .= '}' . PHP_EOL;
  344. file_put_contents('configuration.php', $code);
  345. }
  346. /**
  347. * get header of configuration test file
  348. *
  349. * @return string
  350. */
  351. private function _getHeader()
  352. {
  353. return <<<'EOT'
  354. <?php
  355. /**
  356. * DO NOT EDIT: This file is automatically generated by configGenerator.php
  357. */
  358. class configurationTest extends PHPUnit_Framework_TestCase
  359. {
  360. private static $pasteid = '501f02e9eeb8bcec';
  361. private static $paste = array(
  362. 'data' => '{"iv":"EN39/wd5Nk8HAiSG2K5AsQ","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"QKN1DBXe5PI","ct":"8hA83xDdXjD7K2qfmw5NdA"}',
  363. 'meta' => array(
  364. 'postdate' => 1344803344,
  365. 'opendiscussion' => true,
  366. ),
  367. );
  368. private $_model;
  369. private $_conf;
  370. public function setUp()
  371. {
  372. /* Setup Routine */
  373. $this->_conf = PATH . 'cfg' . DIRECTORY_SEPARATOR . 'conf.ini';
  374. if (!is_file($this->_conf . '.bak') && is_file($this->_conf))
  375. rename($this->_conf, $this->_conf . '.bak');
  376. $this->_model = zerobin_data::getInstance(array('dir' => PATH . 'data'));
  377. serversalt::setPath(PATH . 'data');
  378. $this->reset();
  379. }
  380. public function tearDown()
  381. {
  382. /* Tear Down Routine */
  383. rename($this->_conf . '.bak', $this->_conf);
  384. }
  385. public function reset($configuration = array())
  386. {
  387. $_POST = array();
  388. $_GET = array();
  389. $_SERVER = array();
  390. if ($this->_model->exists(self::$pasteid))
  391. $this->_model->delete(self::$pasteid);
  392. helper::createIniFile($this->_conf, $configuration);
  393. }
  394. EOT;
  395. }
  396. /**
  397. * recursive function to generate configurations based on options
  398. *
  399. * @throws Exception
  400. * @return array
  401. */
  402. private function _generateConfigurations()
  403. {
  404. // recursive factorial function
  405. if (++$this->_iterationCount > self::MAX_ITERATIONS) {
  406. echo 'max iterations reached, stopping', PHP_EOL;
  407. return $this->_configurations;
  408. }
  409. echo "generateConfigurations: iteration $this->_iterationCount", PHP_EOL;
  410. $continue = list($path, $settings) = each($this->_options);
  411. if ($continue === false) {
  412. return $this->_configurations;
  413. }
  414. list($section, $option) = explode('/', $path);
  415. for ($c = 0, $max = count($this->_configurations); $c < $max; ++$c) {
  416. if (!array_key_exists($section, $this->_configurations[$c]['options'])) {
  417. $this->_configurations[$c]['options'][$section] = array();
  418. }
  419. if (count($settings) == 0) {
  420. throw new Exception("Check your \$options: option $option has no settings!");
  421. }
  422. // set the first setting in the original configuration
  423. $setting = current($settings);
  424. $this->_addSetting($this->_configurations[$c], $setting, $section, $option);
  425. // create clones for each of the other settings
  426. while ($setting = next($settings)) {
  427. $clone = $this->_configurations[$c];
  428. $this->_configurations[] = $this->_addSetting($clone, $setting, $section, $option);
  429. }
  430. reset($settings);
  431. }
  432. return $this->_generateConfigurations();
  433. }
  434. /**
  435. * add a setting to the given configuration
  436. *
  437. * @param array $configuration
  438. * @param array $setting
  439. * @param string $section
  440. * @param string $option
  441. * @throws Exception
  442. * @return array
  443. */
  444. private function _addSetting(&$configuration, &$setting, &$section, &$option) {
  445. if (++$this->_iterationCount > self::MAX_ITERATIONS) {
  446. echo 'max iterations reached, stopping', PHP_EOL;
  447. return $configuration;
  448. }
  449. echo "addSetting: iteration $this->_iterationCount", PHP_EOL;
  450. if (
  451. array_key_exists($option, $configuration['options'][$section]) &&
  452. $configuration['options'][$section][$option] === $setting['setting']
  453. ) {
  454. $val = helper::var_export_min($setting['setting'], true);
  455. throw new Exception("Endless loop or error in options detected: option '$option' already exists with setting '$val' in one of the configurations!");
  456. }
  457. $configuration['options'][$section][$option] = $setting['setting'];
  458. $configuration['tests'][$option] = $setting['tests'];
  459. foreach ($setting['affects'] as $affects) {
  460. if (!in_array($affects, $configuration['affects'])) {
  461. $configuration['affects'][] = $affects;
  462. }
  463. }
  464. return $configuration;
  465. }
  466. }