1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 
<?php
    declare(strict_types=1);
    /**
     *  +------------------------------------------------------------+
     *  | apnscp                                                     |
     *  +------------------------------------------------------------+
     *  | Copyright (c) Apis Networks                                |
     *  +------------------------------------------------------------+
     *  | Licensed under Artistic License 2.0                        |
     *  +------------------------------------------------------------+
     *  | Author: Matt Saladna (msaladna@apisnetworks.com)           |
     *  +------------------------------------------------------------+
     */

    /**
     * Testing module for nothing more than unit testing
     *
     * @author Matt Saladna <matt@apisnetworks.com>
     */
    class Test_Module extends Module_Skeleton
    {
        public $exportedFunctions = array(
            '*' => PRIVILEGE_ALL,
        );

        public function __construct()
        {
            parent::__construct();
            if (!is_debug()) {
                $this->exportedFunctions = array('*' => PRIVILEGE_NONE);
            }
        }

        /**
         * Benchmark all callable class methods
         *
         * @param int    $iterations
         * @param string $testclass
         * @return boolean
         */
        public function benchmark_all($iterations = 1000, $testclass = null)
        {
            if (!is_debug()) {
                return error('benchmark only enabled in dev');
            }
            $methods = array();
            if (is_null($testclass)) {
                $my_class = __CLASS__;
            } else {
                $my_class = $testclass;
            }
            if (!class_exists($my_class)) {
                return error("Unknown class `%s'", $my_class);
            }
            $my_method = __FUNCTION__;
            $rfxn = new ReflectionClass($my_class);
            $maxlen = 0;
            foreach ($rfxn->getMethods(ReflectionMethod::IS_PUBLIC) as $m) {
                $method = $m->name;
                $class = $m->class;
                if ($class === $my_class && $method !== $my_method) {
                    $methods[] = $method;
                    $maxlen = max($maxlen, strlen($method));
                }
            }
            $header = 'Module ' . $my_class;
            print  $header . "\n" .
                str_repeat('=', strlen($header)) . "\n";
            foreach ($methods as $m) {
                printf('%-' . $maxlen . 's: ', $m);
                $start = microtime(true);
                for ($i = 0, $n = $iterations; $i < $n; $i++) {
                    assert($ret = $this->$m());
                }
                $end = microtime(true);
                $diff = ($end - $start);
                printf("%.4fs (%.6fs)\n", $diff, $diff / $iterations);
            }
            print "\n";

            return true;
        }

        /**
         * Test execution using named arguments
         *
         * @return bool
         */
        public function exec_named_args()
        {
            $args = array(
                'program' => 'echo',
                'args'    => 'Hello World!'
            );
            $proc = Util_Process_Safe::exec('%(program)s %(args)s %(args)s',
                $args, array(1, 0)
            );

            return $proc['success'];
        }

        /**
         * Test execution without named arguments
         *
         * @return bool
         */
        public function exec_no_args()
        {
            $proc = Util_Process::exec('echo "Hello"',
                array(0)
            );

            return $proc['success'];
        }

        public function exec_fail()
        {
            $proc = Util_Process::exec('/bin/true', array(1));

            return $proc['success'] == false;
        }

        /**
         * -----------------------------------
         *
         * crap tested that I forgot to remove
         *
         * -----------------------------------
         */

        public function exec_additional_args()
        {
            $args = array('echo', "'Hello World!'", 'test');
            // should emit a warning
            $proc = Util_Process::exec('%s %s', 'echo', "'Hello World!'", 'Test');

            return $proc['success'];
        }

        public function exec_quotes()
        {
            $args = array("'Hello World!'", 'test');
            // should emit a warning
            $proc = Util_Process_Safe::exec('echo %s %s', $args);
            print $proc['stdout'];

            return $proc['success'];
        }

        /**
         * Profile backend performance
         */

        public function backend_performance($n = 10000)
        {
            /**
             * Journal of cmd -d debug.com test_backend_performance
             * 2016/09/08: 0.1031ms
             * 2017/04/10: 0.1016ms
             * 2017/05/15: 0.1092ms
             * 2017/06/19: 0.0966ms <-- last i5 760
             * 2017/06/19: 0.1201ms <-- L3426
             * 2017/10/14: 0.1220ms
             * 2017/11/26: 0.1286ms
             * 2018/02/12: 0.1327ms <-- Contextables
             * 2018/09/15: 0.1757ms <-- VM
             * 2018/11/14: 0.1685ms <-- merge accept, no timeout
             * 2019/08/25: 0.1655ms <-- periodic update, same VM
             * 2020/07/13: 0.1014ms <-- Ryzen 5 3600
             *
             */
            return $this->benchmark('test_backend_emitter', $n);
        }

        /**
         * Benchmark apnscp function
         *
         * @param  Closure|string $func fully-qualified function
         * @param  int            $iterations
         * @return float
         */
        public function benchmark($func, int $iterations = 1000)
        {
            if (!is_debug()) {
                return error('benchmark only enabled in dev');
            }
            if ($func instanceof \Closure) {
                $fname = (new ReflectionFunction($func))->getName();
            } else if (is_callable(array($this, $func))) {
                $fname = $func;
                $func = [$this, $func];
            }
            if (!is_callable($func)) {
                return error("function `%s' is not callable", $fname);
            }
            gc_collect_cycles();
            gc_mem_caches();
            $bm = static function (Callable $func, string $fname) use ($iterations) {
                print 'benchmark ' . $fname . "\r\n";
                $start = microtime(true);
                for ($i = 0; $i < $iterations; $i++) {
                    //assert(call_user_func($func) == $resp);
                    $func();
                }
                $end = microtime(true);
                $delta = $end - $start;
                printf("time: %.2f sec (%d rounds; %.4f ms each; %.2f per second)\n\n",
                    $delta,
                    $iterations,
                    $delta / $iterations * 1000,
                    $iterations / $delta
                );

                return $delta;
            };
            $bmf = $bm($func, $fname);
            $bm = null;

            return $bmf;
        }

        /**
         * Frontend emitter to evaluate backend performance
         *
         * @param null $args
         * @return mixed
         */
        public function backend_emitter($args = '')
        {
            //return $this->query('test_backend_collector', $args);
            /*$ds = \DataStream::get();
            $ds->setOption(\apnscpObject::RESET);
            $payload = $ds->pack('test_backend_collector', $args, null, Auth::get_driver()->getID());
            return $ds->writeSocket($payload);*/
            return $this->query('test_backend_collector', $args);
        }

        /**
         * Backend collector to evaluate performance
         *
         * @param null $args
         * @return mixed
         */
        public function backend_collector($args = '')
        {
            return $args;
        }

        public function config_bm()
        {

            $this->compare('test_config', 'test_config2');
        }

        /**
         * Comparatively benchmark functions
         *
         * @param  string $func1
         * @param  string $func2
         * @param  int    $iterations
         * @return int  1 if $func1 faster than $func2, -1 vice-versa
         */
        public function compare($func1, $func2, $iterations = 1000)
        {
            if (!is_debug()) {
                return error('benchmark only enabled in dev');
            }

            if (!$func1 || !$func2) {
                return error('need 2 functions to compare');
            }
            $mem = memory_get_usage();
            $bmf1 = $this->benchmark($func1, $iterations);
            $bmf2 = $this->benchmark($func2, $iterations);
            $ret = 0;
            if ($bmf1 < $bmf2) {
                printf('%s is quicker than %s ', $func1, $func2);
                $diff = abs($bmf1 - $bmf2);
                $diffp = $diff / $bmf2;
                $ret = 1;
            } else {
                printf('%s is quicker than %s ', $func2, $func1);
                $diff = abs($bmf2 - $bmf1);
                $diffp = $diff / $bmf1;
                $ret = -1;
            }
            printf("by %.2f%%\n\n", $diffp * 100);
            printf("Mem usage: %.2f bytes\n", memory_get_usage() - $mem);

            return;
        }

        public function config($conf = null)
        {
            $conf = $this->getAuthContext()->conf('ipinfo');

            //array_unshift($conf, '[DEFAULT]');
            return "[DEFAULT]\n" . Util_Conf::build_ini($conf);
        }

        public function config2($conf = null)
        {
            $conf = $this->getAuthContext()->conf('ipinfo');
            $data = '[DEFAULT]' . "\n";
            foreach ($conf as $srvc_var => $srvc_val) {
                $data .= $srvc_var . ' = ' . (!is_array($srvc_val) ? $srvc_val : (!$srvc_val ? '[]' : '[\'' . implode('\', \'',
                    array_unique($srvc_val)) . '\']')) . "\n";
            }

            return $data;
        }

        public function sudo()
        {
            if (!IS_CLI) {
                return $this->query('test_sudo');
            }
            $args = array('user' => 'debug');
            if ($this->permission_level & PRIVILEGE_ADMIN) {
                $args['domain'] = 'debug.com';
            }
            $ret = Util_Process_Sudo::exec('id',
                $args);

            return $ret;
        }

        public function fn_decompose($cmd)
        {
            return Util_Process::decompose($cmd);
        }

        public function mail()
        {
            $address = $this->common_get_email() ?? Crm_Module::COPY_ADMIN;
            $template = \BladeLite::factory('views/email');
            $html = $template->make('simple',
                [
                    'msg' => 'This is a test email from your panel!' .
                        "\n\nPanel login source: " . \Auth_Redirect::getPreferredUri()
                ]
            )->render();

            $opts = array(
                'html_charset' => 'utf-8',
                'text_charset' => 'utf-8'
            );
            $from = \Crm_Module::FROM_NAME . ' <' . \Crm_Module::FROM_ADDRESS . '>';
            $headers = array(
                'Sender' => $from,
                'From'   => $from
            );
            $mime = new Mail_Mime($opts);

            $mime->setHTMLBody($html);
            $mime->setTXTBody(strip_tags($html));
            $headers = $mime->txtHeaders($headers);
            $msg = $mime->get();

            return Mail::send(
                $address,
                PANEL_BRAND . ' test',
                $msg,
                $headers
            );

            return info("Sent test email to `%s'", $address);
        }

        /**
         * ER wrapper
         *
         * @param string $class
         * @param string $confirmation
         * @return bool|void
         */
        public function message_class(string $class, string $confirmation = 'Hello!') {
            if (!\in_array($class, ['fatal', 'error', 'warning', 'info'], true)) {
                return error("Unknown message class `%s'", $class);
            }
            return $class($confirmation);
        }

        /**
         * Sleep backend for duration
         *
         * @param int $time
         * @return bool
         */
        public function sleep(int $time = 10)
        {
            if (!IS_CLI) {
                return $this->query('test_sleep', $time);
            }
            sleep($time);

            return true;
        }

        /**
         * Get locale-specific time
         *
         * @return false|string
         */
        public function now()
        {
            return date('r');
        }

        public function context_performance()
        {
            /**
             * 2019/01/29: 360 req/sec
             */
            if (!$this->permission_level & PRIVILEGE_SITE) {
                return error('Context requires site admin privileges');
            }

            $oldrep = \Error_Reporter::set_verbose(0);
            $user = \Opcenter\Auth\Password::generate(8, 'a-z');
            if (!$this->user_add($user, 'randompassword12345')) {
                return error('Failed to create user');
            }
            \assert(spl_object_hash(\Auth::context($user, $this->site)) !== spl_object_hash(\Auth::context($user, $this->site)), 'Context uniqueness');
            $this->benchmark(function () use ($user) {
                \Auth::context($user, $this->site);
            });
            $this->user_delete($user);
            \Error_Reporter::clear_buffer();
            \Error_Reporter::set_verbose($oldrep);
        }

        public function metrics(string $attr = null)
        {
            if (!TELEMETRY_ENABLED) {
                return error('[telemetry] => enabled is off in config.ini');
            }

            $pg = PostgreSQL::pdo();
            $query = "SELECT TIME_BUCKET('5 minute', ts), name, label, value FROM metrics " .
                'JOIN metric_attributes USING (attr_id) WHERE site_id = ' . $this->site_id . " AND ts >= NOW() - '1 day'::INTERVAL";
            if ($attr) {
                $query .= ' AND name = ' . $pg->quote($attr);
            }
            $rs = $pg->query($query);
            return $rs->fetchAll(PDO::FETCH_ASSOC);
        }

        public function sigchld_exit(int $code, bool $backend = true): int
        {
            if ($backend && !IS_CLI) {
                return $this->query('test_sigchld_exit', $code, $backend);
            }

            $ret = \Util_Process_Safe::exec('exit %d', [$code]);
            return $ret['return'];
        }
    }