mirror of
https://github.com/librenms/librenms.git
synced 2024-10-07 16:52:45 +00:00
Rewrite development helper to lnms dev:check (#11650)
* Refactor pre-commit to class * docs build * dusk check * ci mode for checks * full checks * other mysql * make other lint checks actually work fix pylint finding * ci is a long opt * fix undefined index * dusk fully working * ask for forgiveness, not permission * fix whitespace * skip dusk sometimes * Handle 3com and other os with digits * flags instead of if else spaghetti * convert to command * cleanup * missed check * fixes * case * self-check :D * argument now * fix bugs from refactors * another fix * adjust file change parsing * refactor execut a bit * fallback to global quiet when unknown type. * allow quiet override for specific commands * output cleanup * check flow * start of tests * file categorizer tests and fixes * fixes and cleanup * skipable not implemented... * more tests, fix bugs * more tests and cleanup * wrong command * fix canCheck and set env properly * full env fix * don't allow dusk on user's run as it will erase their db. * fix os option * fix whitespace * don't need to start server * ci doesn't like that
This commit is contained in:
@@ -1,456 +1,5 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
$filename = basename(__FILE__);
|
||||
$install_dir = realpath(__DIR__ . '/..');
|
||||
chdir($install_dir);
|
||||
|
||||
require $install_dir . '/vendor/autoload.php';
|
||||
|
||||
if (getenv('FILES')) {
|
||||
$changed_files = rtrim(getenv('FILES'));
|
||||
} else {
|
||||
$changed_files = exec("git diff --diff-filter=d --name-only master | tr '\n' ' '|sed 's/,*$//g'");
|
||||
}
|
||||
|
||||
$changed_files = $changed_files ? explode(' ', $changed_files) : [];
|
||||
|
||||
$map = [
|
||||
'docs' => 0,
|
||||
'python' => 0,
|
||||
'bash' => 0,
|
||||
'php' => 0,
|
||||
'os-php' => 0,
|
||||
'os' => [],
|
||||
];
|
||||
|
||||
foreach ($changed_files as $file) {
|
||||
if (Str::startsWith($file, 'doc/')) {
|
||||
$map['docs']++;
|
||||
}
|
||||
if (Str::endsWith($file, '.py')) {
|
||||
$map['python']++;
|
||||
}
|
||||
if (Str::endsWith($file, '.sh')) {
|
||||
$map['bash']++;
|
||||
}
|
||||
|
||||
// cause full tests to run
|
||||
if ($file == 'composer.lock' || $file == '.travis.yml') {
|
||||
$map['php']++;
|
||||
}
|
||||
|
||||
// check if os owned file or generic php file
|
||||
if (!empty($os_name = os_from_file($file))) {
|
||||
$map['os'][] = $os_name;
|
||||
if (Str::endsWith($file, '.php')) {
|
||||
$map['os-php']++;
|
||||
}
|
||||
} elseif (Str::endsWith($file, '.php')) {
|
||||
$map['php']++;
|
||||
}
|
||||
}
|
||||
|
||||
$map['os'] = array_unique($map['os']);
|
||||
|
||||
$short_opts = 'lsufqcho:m:';
|
||||
$long_opts = array(
|
||||
'lint',
|
||||
'style',
|
||||
'unit',
|
||||
'os:',
|
||||
'module:',
|
||||
'fail-fast',
|
||||
'quiet',
|
||||
'snmpsim',
|
||||
'db',
|
||||
'commands',
|
||||
'help',
|
||||
);
|
||||
$options = getopt($short_opts, $long_opts);
|
||||
|
||||
if (check_opt($options, 'h', 'help')) {
|
||||
echo "LibreNMS Code Tests Script
|
||||
Running $filename without options runs all checks.
|
||||
-l, --lint Run php lint checks to test for valid syntax
|
||||
-s, --style Run phpcs check to check for PSR-2 compliance
|
||||
-u, --unit Run phpunit tests
|
||||
-o, --os Specific OS to run tests on. Implies --unit, --db, --snmpsim
|
||||
-m, --module Specific Module to run tests on. Implies --unit, --db, --snmpsim
|
||||
-f, --fail-fast Quit when any failure is encountered
|
||||
-q, --quiet Hide output unless there is an error
|
||||
--db Run unit tests that require a database
|
||||
--snmpsim Use snmpsim for unit tests
|
||||
-c, --commands Print commands only, no checks
|
||||
-h, --help Show this help text.\n";
|
||||
exit();
|
||||
}
|
||||
|
||||
// set up some variables
|
||||
$passthru = !check_opt($options, 'q', 'quiet');
|
||||
$command_only = check_opt($options, 'c', 'commands');
|
||||
$fail_fast = check_opt($options, 'f', 'fail-fast');
|
||||
$return = 0;
|
||||
$completed_tests = array(
|
||||
'lint' => false,
|
||||
'style' => false,
|
||||
'unit' => false,
|
||||
);
|
||||
$docs_only = false;
|
||||
|
||||
if ($os = check_opt($options, 'os', 'o')) {
|
||||
// enable unit tests, snmpsim, and db
|
||||
$options['u'] = false;
|
||||
$options['snmpsim'] = false;
|
||||
$options['db'] = false;
|
||||
}
|
||||
|
||||
if ($module = check_opt($options, 'm', 'module')) {
|
||||
putenv("TEST_MODULES=$module");
|
||||
// enable unit tests, snmpsim, and db
|
||||
$options['u'] = false;
|
||||
$options['snmpsim'] = false;
|
||||
$options['db'] = false;
|
||||
}
|
||||
|
||||
$all = !check_opt($options, 'l', 'lint', 's', 'style', 'u', 'unit');
|
||||
if ($all) {
|
||||
// no test specified, run all tests in this order
|
||||
$options += array('u' => false, 's' => false, 'l' => false);
|
||||
}
|
||||
|
||||
if (check_opt($options, 'snmpsim')) {
|
||||
putenv('SNMPSIM=1');
|
||||
}
|
||||
|
||||
if (check_opt($options, 'db')) {
|
||||
putenv('DBTEST=1');
|
||||
}
|
||||
|
||||
// No php files, skip the php checks.
|
||||
if (!empty($changed_files) && $map['php'] === 0 && $map['os-php'] === 0) {
|
||||
putenv('SKIP_LINT_CHECK=1');
|
||||
putenv('SKIP_STYLE_CHECK=1');
|
||||
}
|
||||
|
||||
// If we have no php files and no OS' found then also skip unit checks.
|
||||
if (!empty($changed_files) && $map['php'] === 0 && empty($map['os']) && !$os) {
|
||||
if ($map['docs'] > 0) {
|
||||
$docs_only = true;
|
||||
} else {
|
||||
putenv('SKIP_UNIT_CHECK=1');
|
||||
}
|
||||
}
|
||||
|
||||
// If we have more than 4 (arbitrary number) of OS' then blank them out
|
||||
// Unit tests may take longer to run in a loop so fall back to all.
|
||||
if (count($map['os']) > 4) {
|
||||
unset($map['os']);
|
||||
}
|
||||
|
||||
// run tests in the order they were specified
|
||||
|
||||
foreach (array_keys($options) as $opt) {
|
||||
$ret = 0;
|
||||
if ($opt == 'l' || $opt == 'lint') {
|
||||
$ret = run_check('lint', $passthru, $command_only);
|
||||
} elseif ($opt == 's' || $opt == 'style') {
|
||||
$ret = run_check('style', $passthru, $command_only);
|
||||
} elseif ($opt == 'u' || $opt == 'unit') {
|
||||
if (!empty($map['os']) && $map['php'] === 0) {
|
||||
$os = $map['os'];
|
||||
}
|
||||
|
||||
if (!empty($os)) {
|
||||
echo 'Only checking os: ' . implode(', ', (array)$os) . PHP_EOL;
|
||||
}
|
||||
|
||||
$ret = run_check('unit', $passthru, $command_only, compact('fail_fast', 'os', 'module', 'docs_only'));
|
||||
}
|
||||
|
||||
if ($fail_fast && $ret !== 0 && $ret !== 250) {
|
||||
exit($ret);
|
||||
} else {
|
||||
$return += $ret;
|
||||
}
|
||||
}
|
||||
|
||||
// output Tests ok, if no arguments passed
|
||||
if ($all && $return === 0) {
|
||||
echo "\033[32mTests ok, submit away :)\033[0m \n";
|
||||
}
|
||||
exit($return); //return the combined/single return value of tests
|
||||
|
||||
function os_from_file($file)
|
||||
{
|
||||
if (Str::startsWith($file, 'includes/definitions/')) {
|
||||
return basename($file, '.yaml');
|
||||
} elseif (Str::startsWith($file, ['includes/polling', 'includes/discovery'])) {
|
||||
return os_from_php($file);
|
||||
} elseif (Str::startsWith($file, 'LibreNMS/OS/')) {
|
||||
if (preg_match('#LibreNMS/OS/[^/]+.php#', $file)) {
|
||||
// convert class name to os name
|
||||
preg_match_all("/[A-Z][a-z]*/", basename($file, '.php'), $segments);
|
||||
$osname = implode('-', array_map('strtolower', $segments[0]));
|
||||
$os = os_from_php($osname);
|
||||
if ($os) {
|
||||
return $os;
|
||||
}
|
||||
return os_from_php(str_replace('-', '_', $osname));
|
||||
}
|
||||
} elseif (Str::startsWith($file, ['tests/snmpsim/', 'tests/data/'])) {
|
||||
list($os,) = explode('_', basename(basename($file, '.json'), '.snmprec'), 2);
|
||||
return $os;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract os name from path and validate it exists.
|
||||
*
|
||||
* @param $php_file
|
||||
* @return null|string
|
||||
*/
|
||||
function os_from_php($php_file)
|
||||
{
|
||||
$os = basename($php_file, '.inc.php');
|
||||
|
||||
if (file_exists("includes/definitions/$os.yaml")) {
|
||||
return $os;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Run the specified check and return the return value.
|
||||
* Make sure it isn't skipped by SKIP_TYPE_CHECK env variable and hasn't been run already
|
||||
*
|
||||
* @param string $type type of check lint, style, or unit
|
||||
* @param bool $passthru display the output as comes in
|
||||
* @param bool $command_only only display the intended command, no checks
|
||||
* @param array $options command specific options
|
||||
* @return int the return value from the check (0 = success)
|
||||
*/
|
||||
function run_check($type, $passthru, $command_only, $options = array())
|
||||
{
|
||||
global $completed_tests;
|
||||
if (getenv('SKIP_' . strtoupper($type) . '_CHECK') || $completed_tests[$type]) {
|
||||
echo ucfirst($type) . " check skipped.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
$function = 'check_' . $type;
|
||||
if (function_exists($function)) {
|
||||
$completed_tests[$type] = true;
|
||||
return $function($passthru, $command_only, $options);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs php -l and tests for any syntax errors
|
||||
*
|
||||
* @param bool $passthru display the output as comes in
|
||||
* @param bool $command_only only display the intended command, no checks
|
||||
* @return int the return value from running php -l (0 = success)
|
||||
*/
|
||||
function check_lint($passthru = false, $command_only = false)
|
||||
{
|
||||
$parallel_lint_bin = check_exec('parallel-lint');
|
||||
|
||||
// matches a substring of the relative path, leading / is treated as absolute path
|
||||
$lint_excludes = array('vendor/');
|
||||
|
||||
|
||||
$lint_exclude = build_excludes('--exclude ', $lint_excludes);
|
||||
$lint_cmd = "$parallel_lint_bin $lint_exclude ./";
|
||||
|
||||
if ($command_only) {
|
||||
echo $lint_cmd . PHP_EOL;
|
||||
return 250;
|
||||
}
|
||||
|
||||
echo 'Running lint check... ';
|
||||
|
||||
if ($passthru) {
|
||||
echo PHP_EOL;
|
||||
passthru($lint_cmd, $lint_ret);
|
||||
} else {
|
||||
exec($lint_cmd, $lint_output, $lint_ret);
|
||||
|
||||
if ($lint_ret > 0) {
|
||||
print(implode(PHP_EOL, $lint_output) . PHP_EOL);
|
||||
} else {
|
||||
echo "success\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $lint_ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs phpcs --standard=PSR2 against the code base
|
||||
*
|
||||
* @param bool $passthru display the output as comes in
|
||||
* @param bool $command_only only display the intended command, no checks
|
||||
* @return int the return value from phpcs (0 = success)
|
||||
*/
|
||||
function check_style($passthru = false, $command_only = false)
|
||||
{
|
||||
$phpcs_bin = check_exec('phpcs');
|
||||
|
||||
$cs_cmd = "$phpcs_bin -n -p --colors --extensions=php --standard=misc/phpcs_librenms.xml ./";
|
||||
|
||||
if ($command_only) {
|
||||
echo $cs_cmd . PHP_EOL;
|
||||
return 250;
|
||||
}
|
||||
|
||||
echo 'Running style check... ';
|
||||
|
||||
if ($passthru) {
|
||||
echo PHP_EOL;
|
||||
passthru($cs_cmd, $cs_ret);
|
||||
} else {
|
||||
exec($cs_cmd, $cs_output, $cs_ret);
|
||||
|
||||
if ($cs_ret > 0) {
|
||||
echo "failed\n";
|
||||
print(implode(PHP_EOL, $cs_output) . PHP_EOL);
|
||||
} else {
|
||||
echo "success\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $cs_ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs phpunit
|
||||
*
|
||||
* @param bool $passthru display the output as comes in
|
||||
* @param bool $command_only only display the intended command, no checks
|
||||
* @param array $options Supported: fail_fast, os, module
|
||||
* @return int the return value from phpunit (0 = success)
|
||||
*/
|
||||
function check_unit($passthru = false, $command_only = false, $options = array())
|
||||
{
|
||||
echo 'Running unit tests... ';
|
||||
|
||||
$phpunit_bin = check_exec('phpunit');
|
||||
|
||||
$phpunit_cmd = "$phpunit_bin --colors=always";
|
||||
|
||||
if ($options['fail_fast']) {
|
||||
$phpunit_cmd .= ' --stop-on-error --stop-on-failure';
|
||||
}
|
||||
|
||||
if ($options['os']) {
|
||||
$filter = implode('.*|', (array)$options['os']);
|
||||
// include tests that don't have data providers and only data sets that match
|
||||
$phpunit_cmd .= " --group os --filter '/::test[A-Za-z]+$|::test[A-Za-z]+ with data set \"$filter.*\"$/'";
|
||||
}
|
||||
|
||||
if ($options['docs_only']) {
|
||||
$phpunit_cmd .= " --group docs";
|
||||
}
|
||||
|
||||
if ($options['module']) {
|
||||
$phpunit_cmd .= ' tests/OSModulesTest.php';
|
||||
}
|
||||
|
||||
if ($command_only) {
|
||||
echo $phpunit_cmd . PHP_EOL;
|
||||
return 250;
|
||||
}
|
||||
|
||||
if ($passthru) {
|
||||
echo PHP_EOL;
|
||||
passthru($phpunit_cmd, $phpunit_ret);
|
||||
} else {
|
||||
exec($phpunit_cmd, $phpunit_output, $phpunit_ret);
|
||||
|
||||
if ($phpunit_ret > 0) {
|
||||
echo "failed\n";
|
||||
echo implode(PHP_EOL, $phpunit_output) . PHP_EOL;
|
||||
echo 'snmpsimd: output at /tmp/snmpsimd.log';
|
||||
} else {
|
||||
echo "success\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $phpunit_ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given options array contains any of the $opts specified
|
||||
*
|
||||
* @param array $options the array from getopt()
|
||||
* @param string ...$opts options to check for
|
||||
* @return bool If one of the specified options is set
|
||||
*/
|
||||
function check_opt($options, ...$opts)
|
||||
{
|
||||
foreach ($opts as $option) {
|
||||
if (isset($options[$option])) {
|
||||
if ($options[$option] === false) {
|
||||
// no data, return that option is enabled
|
||||
return true;
|
||||
}
|
||||
return $options[$option];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a list of exclude arguments from an array
|
||||
*
|
||||
* @param string $exclude_string such as "--exclude"
|
||||
* @param array $excludes array of directories to exclude
|
||||
* @return string resulting string
|
||||
*/
|
||||
function build_excludes($exclude_string, $excludes)
|
||||
{
|
||||
$result = '';
|
||||
foreach ($excludes as $exclude) {
|
||||
$result .= $exclude_string . $exclude . ' ';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for an executable and return the path to it
|
||||
* If it does not exist, run composer update.
|
||||
* If composer isn't installed, print error and exit.
|
||||
*
|
||||
* @param string $exec the name of the executable to check
|
||||
* @return string path to the executable
|
||||
*/
|
||||
function check_exec($exec)
|
||||
{
|
||||
$path = "vendor/bin/$exec";
|
||||
|
||||
if (is_executable($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
echo "Running composer install to install developer dependencies.\n";
|
||||
passthru("scripts/composer_wrapper.php install");
|
||||
|
||||
if (is_executable($path)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
echo "\nRunning installing deps with composer failed.\n You should try running './scripts/composer_wrapper.php install' by hand\n";
|
||||
echo "You can find more info at http://docs.librenms.org/Developing/Validating-Code/\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "Use ./lnms dev:check\n";
|
||||
exit(1);
|
||||
|
||||
Reference in New Issue
Block a user