$target[1].' = ... '.strtolower($token[1]), 'line' => $target[2], ); break; } } } return $found; } echo "the scan can tell the difference\n"; // Given before the tree-wide check, because a scan that matched nothing would // pass that check on its own. // A helper so the self-checks read as the keys they are about. function keys_of($found) { $keys = array(); foreach ($found as $f) $keys[] = $f['key']; return $keys; } check('an assignment cut short by or is caught', keys_of(cut_short_assignments('Schedule() === \'\') or ($parts !== null);')), array('$drawable = ... or')); echo "nothing new under web/ cuts an assignment short\n"; // What the scan found in the rest of the tree when this test was written. // This is a list of open questions, not a list of things that are fine. // // Two of them are the deliberate idiom: "$fp = fopen(...) or error_log(...)" // relies on the loose binding on purpose, and the variable does get the fopen // result. They are listed because the scan cannot tell intent, and because // writing the failure branch out as an if would let them come off the list. // // The other four are the same bug as the one this test was written for, and // each changes behaviour to fix, so they are left for a decision rather than // swept up here: // // filter.php:400 $canSave = !$filter->Id() or $canEdit // -- reads as "a new filter can always be saved, an // existing one only with edit rights", but assigns only // !Id(), so an existing filter is never savable. // filter.php:401 $canDelete = $filter->Id() and $canEdit // -- assigns only Id(), so delete is offered for any // existing filter whether or not the user may edit it. // This one is access control, so it is not mine to guess at. // console.php:110 $show_storage_areas -- assigns only the count test, so // the canEdit('System') half never applies. // monitor.php:1483 $stream_available = canView('Stream') and ... or ... // -- assigns only canView('Stream'). The intent is also // ambiguous between A && (B || C) and (A && B) || C, which // is a second reason not to rewrite it unasked. // // Anything not on this list fails, so a new one cannot be added -- including // the one this test was written for, which is deliberately absent. $known = array( 'web/includes/config.php $cfg = ... or', 'web/skins/classic/views/_options_dnsmasq.php $cfg = ... or', 'web/skins/classic/views/filter.php $canSave = ... or', 'web/skins/classic/views/filter.php $canDelete = ... and', 'web/skins/classic/views/console.php $show_storage_areas = ... and', 'web/skins/classic/views/monitor.php $stream_available = ... and', ); $offenders = array(); $where = array(); // key => path:line, for the report only $scanned = 0; $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root.'/web')); foreach ($iterator as $file) { if ($file->isDir() or $file->getExtension() !== 'php') continue; $path = $file->getPathname(); // web/api is cakephp, assets are third party. if (strpos($path, '/api/') !== false) continue; if (strpos($path, '/assets/') !== false) continue; $scanned++; foreach (cut_short_assignments(file_get_contents($path)) as $find) { $relative = substr($path, strlen($root) + 1); $offenders[] = $relative.' '.$find['key']; $where[$relative.' '.$find['key']] = $relative.':'.$find['line']; } } check('a useful number of files were scanned', $scanned > 100, true); sort($offenders); $expected = $known; sort($expected); check('nothing cuts an assignment short that was not already on the list', array_values(array_diff($offenders, $expected)), array()); // The other direction, so the list shrinks as they are dealt with rather than // sitting here forever describing lines that no longer look like that. check('and every line on the list is still there', array_values(array_diff($expected, $offenders)), array()); echo "the actions table's schedule reads as a sentence again\n"; // The line this was all for. Checked by name rather than by scanning, because // its absence from the list above would pass quietly if the line were deleted // along with the feature. $view = file_get_contents($root.'/web/skins/classic/views/monitor.php'); check('the drawable test is a single expression', (bool)preg_match('/\$drawable\s*=\s*\(.*?\)\s*\|\|\s*\(.*?\);/', $view), true); check('and does not use the loose or', (bool)preg_match('/\$drawable\s*=[^;]*\bor\b/', $view), false); // What it has to decide, stated once: an unset schedule draws (the pickers get // their defaults), a schedule cronToParts understands draws, and one it does // not is shown as cron. require_once($root.'/web/includes/CronTab.php'); foreach (array('' => true, '0 21 * * *' => true, '30 22 * * 1' => true, '*/5 * * * *' => false, 'nonsense' => false) as $spec => $want) { $parts = cronToParts((string)$spec); check('"'.$spec.'" '.($want ? 'draws' : 'shows as cron'), (((string)$spec === '') || ($parts !== null)), $want); } echo "\n$passed passed, $failed failed\n"; exit($failed ? 1 : 0);