/** * Analyzes code coverage results collected from XDebug, and performs coverage density analysis. * * @param object $report The report instance running this filter and aggregating results * @param array $classes A list of classes to analyze coverage on. By default, gets all * defined subclasses of lithium\test\Unit which are currently in memory. * @return array|void Returns an array indexed by file and line, showing the number of * instances each line was called. */ public static function analyze($report, array $classes = array()) { $data = static::collect($report->results['filters'][__CLASS__]); $classes = $classes ?: array_filter(get_declared_classes(), function ($class) use($data) { $unit = 'lithium\\test\\Unit'; return !is_subclass_of($class, $unit) || array_key_exists($class, $data); }); $classes = array_values(array_intersect((array) $classes, array_keys($data))); $densities = $result = array(); foreach ($classes as $class) { $classMap = array($class => Libraries::path($class)); $densities += static::_density($data[$class], $classMap); } $executableLines = array(); if (!empty($classes)) { $executableLines = array_combine($classes, array_map(function ($cls) { return Inspector::executable($cls, array('public' => false)); }, $classes)); } foreach ($densities as $class => $density) { $executable = $executableLines[$class]; $covered = array_intersect(array_keys($density), $executable); $uncovered = array_diff($executable, $covered); $percentage = round(count($covered) / (count($executable) ?: 1), 4) * 100; $result[$class] = compact('class', 'executable', 'covered', 'uncovered', 'percentage'); } $result = static::collectLines($result); return $result; }
/** * Creates step list for this exercise. * * @return void */ protected function _initSteps() { $methods = Inspector::methods($this)->to('array'); foreach ($methods as $method) { if (substr($method['name'], 0, strlen($this->_methodPrefix)) === $this->_methodPrefix) { $this->_steps[] = $method['name']; } } }
/** * "Explain" methods step. * * @return void */ public function explainExplainMethods() { $this->header("The Explain Methods"); $this->out("The next step is to create your first `explain` method. When an exercise is run, each user-defined method that begins with 'explain' is run in the order it was defined.\n"); $this->out("Let's start by defining a new method called `explainIntro`."); $this->halt(); $methods = \lithium\analysis\Inspector::methods('\\li3_exercises\\extensions\\exercises\\Blog', 'extents'); $this->assertTrue(isset($methods['explainIntro']), "Hmmm. I can't seem to find a method defined on your new Blog class named '{:purple}explainIntro{:end}'. {:error}Make sure it has public visibility!{:end}"); }
/** * Get the methods to test. * * @param string $request * @return string */ protected function _methods($request) { $use = $this->_use($request); $path = Libraries::path($use); if (!file_exists($path)) { return ""; } $methods = (array) Inspector::methods($use, 'extents'); $testMethods = array(); foreach (array_keys($methods) as $method) { $testMethods[] = "\tpublic function test" . ucwords($method) . "() {}"; } return join("\n", $testMethods); }
/** * Takes an instance of an object (usually a Collection object) containing test * instances. Introspects the test subject classes to extract cyclomatic complexity data. * * @param object $report Instance of Report which is calling apply. * @param array $tests The test to apply this filter on * @param array $options Not used. * @return object|void Returns the instance of `$tests`. */ public static function apply($report, $tests, array $options = array()) { $results = array(); foreach ($tests->invoke('subject') as $class) { $results[$class] = array(); if (!($methods = Inspector::methods($class, 'ranges', array('public' => false)))) { continue; } foreach ($methods as $method => $lines) { $lines = Inspector::lines($class, $lines); $branches = Parser::tokenize(join("\n", (array) $lines), array('include' => static::$_include)); $results[$class][$method] = count($branches) + 1; $report->collect(__CLASS__, $results); } } return $tests; }
/** * Returns all classes directly depending on a given class. * * @param string $dependency The class name to use as a dependency. * @param string $exclude Regex path exclusion filter. * @return array Classes having a direct dependency on `$dependency`. May contain duplicates. */ protected static function _affected($dependency, $exclude = null) { $exclude = $exclude ?: '/(tests|webroot|resources|libraries|plugins)/'; $classes = Libraries::find(true, compact('exclude') + array('recursive' => true)); $dependency = ltrim($dependency, '\\'); $affected = array(); foreach ($classes as $class) { if (isset(static::$_cachedDepends[$class])) { $depends = static::$_cachedDepends[$class]; } else { $depends = Inspector::dependencies($class); $depends = array_map(function ($c) { return ltrim($c, '\\'); }, $depends); static::$_cachedDepends[$class] = $depends; } if (in_array($dependency, $depends)) { $affected[] = $class; } } return $affected; }
/** * Runs main console application logic. Returns a list of * available exercises by default. Otherwise runs the specified exercise. * * @param string $command * @return void */ public function run($command = null) { if ($command == null) { $this->out("{:heading}EXERCISES{:end}"); foreach ($this->_exercises as $key => $exercise) { $library = strtok($exercise, '\\'); $info = Inspector::info($exercise); $this->out($this->_pad($key . " {:blue}via {$library}{:end}"), 'heading'); $this->out($this->_pad($info['description'] == '' ? '(No description)' : $info['description']), 2); } $this->stop(); } if (isset($this->_exercises[$command])) { $className = $this->_exercises[$command]; $exercise = new $className(array('command' => $this)); $exercise->run(); } else { $this->out("{:error}The exercise {:end}{:blue}\"{$command}\"{:end}{:error} you specified cannot be found. Please supply a valid exercise name.{:end}"); $this->out(); $this->stop(1); } }
/** * Generate test cases in the given namespace. * `li3 create test model Post` * `li3 create test --library=li3_plugin model Post` * * @param string $type namespace of the class (e.g. model, controller, some.name.space). * @param string $name Name of class to test. * @return void */ public function run($type = null, $name = null) { $library = Libraries::get($this->library); if (empty($library['prefix'])) { return false; } $namespace = $this->_namespace($type); $use = "\\{$library['prefix']}{$namespace}\\{$name}"; $methods = array(); if (class_exists($use)) { $methods = array(); foreach (array_keys(Inspector::methods($use, 'extents')) as $method) { $methods[] = "\tpublic function test" . ucwords($method) . "() {}"; } } $params = array('namespace' => "{$library['prefix']}tests\\cases\\{$namespace}", 'use' => $use, 'class' => "{$name}Test", 'methods' => join("\n", $methods)); if ($this->_save($this->template, $params)) { $this->out("{$params['class']} created for {$name} in {$params['namespace']}."); return true; } return false; }
private function getMap($class) { $properties = Inspector::properties($class, array('public' => false)); //parse out fields $fields = array(); /** @var \ReflectionProperty $p */ foreach ($properties as $p) { if (($p->getModifiers() & \ReflectionProperty::IS_PROTECTED) == \ReflectionProperty::IS_PROTECTED) { $name = $p->getName(); if ($name[0] != '_') { $fields[$name] = Docblock::comment($p->getDocComment()); } } } //Parse out types $ret = array(); foreach ($fields as $field => $data) { if (isset($data['tags']['var'])) { $ret[$field] = $this->dynamicType($data['tags']['var']); } } return $ret; }
/** * Custom check to determine if our given magic methods can be responded to. * * @param string $method Method name. * @param bool $internal Interal call or not. * @return bool */ public function respondsTo($method, $internal = false) { $class = $this->_model; $modelRespondsTo = false; $parentRespondsTo = parent::respondsTo($method, $internal); $staticRespondsTo = $class::respondsTo($method, $internal); if (method_exists($class, '_object')) { $model = $class::invokeMethod('_object'); $modelRespondsTo = $model->respondsTo($method); } else { $modelRespondsTo = Inspector::isCallable($class, $method, $internal); } return $parentRespondsTo || $staticRespondsTo || $modelRespondsTo; }
public function _parseClass($class) { $data = array(); // $methods = Inspector::methods($class, null, array('public' => false)); $methods = get_class_methods($class); $properties = array_keys(get_class_vars($class)); $ident = $class; $info = Inspector::info($ident); $info = Docblock::comment($info['comment']); $data = $this->_merge($data, array('id' => $info['description'], 'comments' => array($ident))); $this->_merge($data, array('id' => $info['text'], 'comments' => array($class))); foreach ($methods as $method) { $ident = "{$class}::{$method}()"; $info = Inspector::info($ident); $info = Docblock::comment($info['comment']); $this->_merge($data, array('id' => $info['description'], 'comments' => array($ident))); $this->_merge($data, array('id' => $info['text'], 'comments' => array($ident))); if (isset($info['tags']['return'])) { $this->_merge($data, array('id' => $info['tags']['return'], 'comments' => array($ident))); } foreach (Set::extract($info, '/tags/params/text') as $text) { $this->_merge($data, array('id' => $text, 'comments' => array($ident))); } } foreach ($properties as $property) { $ident = "{$class}::\${$property}"; $info = Inspector::info($ident); $info = Docblock::comment($info['comment']); $data = $this->_merge($data, array('id' => $info['description'], 'comments' => array($ident))); $data = $this->_merge($data, array('id' => $info['text'], 'comments' => array($ident))); } return $data; }
/** * This docblock has an extra * in the closing element. * */ public function testBadlyClosedDocblock() { $info = Inspector::info(__METHOD__ . '()'); $description = 'This docblock has an extra * in the closing element.'; $this->assertEqual($description, $info['description']); $this->assertEqual('', $info['text']); }
/** * Get the properties for the class * * @param string $class * @param array $options * @return array */ protected function _properties($class, $options = array()) { $defaults = array('name' => null); $options += $defaults; $properties = Inspector::properties($class); $results = array(); foreach ($properties as &$property) { $comment = Docblock::comment($property['docComment']); $description = trim($comment['description']); $type = isset($comment['tags']['var']) ? strtok($comment['tags']['var'], ' ') : null; $name = str_replace('_', '-', Inflector::underscore($property['name'])); $usage = $type == 'boolean' ? "-{$name}" : "--{$name}=" . strtoupper($name); $results[$name] = compact('name', 'description', 'type', 'usage'); if ($name == $options['name']) { return array($name => $results[$name]); } } return $results; }
/** * Will determine if a method can be called. * * @param string $method Method name. * @param bool $internal Interal call or not. * @return bool */ public static function respondsTo($method, $internal = false) { return Inspector::isCallable(get_called_class(), $method, $internal); }
/** * Convert an exception object to an exception result array for test reporting. * * @param object $exception The exception object to report on. Statistics are gathered and * added to the reporting stack contained in `Unit::$_results`. * @param string $lineFlag * @return void * @todo Refactor so that reporters handle trace formatting. */ protected function _reportException($exception, $lineFlag = null) { $initFrame = current($exception['trace']) + array('class' => '-', 'function' => '-'); foreach ($exception['trace'] as $frame) { if (isset($scopedFrame)) { break; } if (!class_exists('lithium\\analysis\\Inspector')) { continue; } if (isset($frame['class']) && in_array($frame['class'], Inspector::parents($this))) { $scopedFrame = $frame; } } $trace = $exception['trace']; unset($exception['trace']); $this->_result('exception', $exception + array('class' => $initFrame['class'], 'method' => $initFrame['function'], 'trace' => Debugger::trace(array('trace' => $trace, 'format' => '{:functionRef}, line {:line}', 'includeScope' => false, 'scope' => array_filter(array('functionRef' => __NAMESPACE__ . '\\{closure}', 'line' => $lineFlag)))))); }
/** * Helper method for caching closure function references to help the process of building the * stack trace. * * @param array $frame Backtrace information. * @param callable|string $function The method related to $frame information. * @return string Returns either the cached or the fetched closure function reference while * writing its reference to the cache array `$_closureCache`. */ protected static function _closureDef($frame, $function) { $reference = '::'; $frame += array('file' => '??', 'line' => '??'); $cacheKey = "{$frame['file']}@{$frame['line']}"; if (isset(static::$_closureCache[$cacheKey])) { return static::$_closureCache[$cacheKey]; } if ($class = Inspector::classes(array('file' => $frame['file']))) { foreach (Inspector::methods(key($class), 'extents') as $method => $extents) { $line = $frame['line']; if (!($extents[0] <= $line && $line <= $extents[1])) { continue; } $class = key($class); $reference = "{$class}::{$method}"; $function = "{$reference}()::{closure}"; break; } } else { $reference = $frame['file']; $function = "{$reference}::{closure}"; } $line = static::_definition($reference, $frame['line']) ?: '?'; $function .= " @ {$line}"; return static::$_closureCache[$cacheKey] = $function; }
<h3 id="source">Source</h3> <div id="sourceCode"></div> <h3>Stack Trace</h3> <div class="lithium-stack-trace"> <ol> <?php foreach ($stack as $id => $frame) { ?> <?php $location = "{$frame['file']}: {$frame['line']}"; $lines = range($frame['line'] - $context, $frame['line'] + $context); $code = Inspector::lines($frame['file'], $lines); ?> <li> <tt><a href="#source" id="<?php echo $id; ?> " class="display-source-excerpt"> <?php echo $frame['functionRef']; ?> </a></tt> <div id="sourceCode<?php echo $id; ?> " style="display: none;">
/** * Output the formatted available commands. * * @return void */ protected function _renderCommands() { $commands = Libraries::locate('command', null, array('recursive' => false)); foreach ($commands as $key => $command) { $library = strtok($command, '\\'); if (!$key || strtok($commands[$key - 1], '\\') != $library) { $this->out("{:heading}COMMANDS{:end} {:blue}via {$library}{:end}"); } $info = Inspector::info($command); $name = strtolower(Inflector::slug($info['shortName'])); $this->out($this->_pad($name), 'heading'); $this->out($this->_pad($info['description']), 2); } $message = 'See `{:command}li3 help COMMAND{:end}`'; $message .= ' for more information on a specific command.'; $this->out($message, 2); }
/** * This is a helper method for testStorageMechanism to fetch a private * property of the Inflector class. * * @param string $property * @return string The value of the property. */ private function getProtectedValue($property) { $info = Inspector::info("lithium\\util\\Inflector::{$property}"); return $info['value']; }
/** * Determines if a given method can be called. * * @param string $method Name of the method. * @param boolean $internal Provide `true` to perform check from inside the * class/object. When `false` checks also for public visibility; * defaults to `false`. * @return boolean Returns `true` if the method can be called, `false` otherwise. */ public function respondsTo($method, $internal = false) { if (method_exists($class = $this->_model, '_object')) { $result = $class::invokeMethod('_object')->respondsTo($method); } else { $result = Inspector::isCallable($class, $method, $internal); } $result = $result || parent::respondsTo($method, $internal); $result = $result || $class::respondsTo($method, $internal); return $result; }
/** * Tests getting static and non-static properties from various types of classes. * * @return void */ public function testGetClassProperties() { $result = array_map(function ($property) { return $property['name']; }, Inspector::properties(__CLASS__)); $expected = array('test', 'test2'); $this->assertEqual($expected, $result); $result = array_map(function ($property) { return $property['name']; }, Inspector::properties(__CLASS__, array('public' => false))); $expected = array('test', 'test2', '_test'); $this->assertEqual($expected, $result); $result = Inspector::properties(__CLASS__); $expected = array(array('modifiers' => array('public'), 'docComment' => false, 'name' => 'test', 'value' => null), array('modifiers' => array('public', 'static'), 'docComment' => false, 'name' => 'test2', 'value' => 'bar')); $this->assertEqual($expected, $result); $result = array_map(function ($property) { return $property['name']; }, Inspector::properties('lithium\\action\\Controller')); $this->assertTrue(in_array('request', $result)); $this->assertTrue(in_array('response', $result)); $this->assertFalse(in_array('_render', $result)); $this->assertFalse(in_array('_classes', $result)); $result = array_map(function ($property) { return $property['name']; }, Inspector::properties('lithium\\action\\Controller', array('public' => false))); $this->assertTrue(in_array('request', $result)); $this->assertTrue(in_array('response', $result)); $this->assertTrue(in_array('_render', $result)); $this->assertTrue(in_array('_classes', $result)); $this->assertNull(Inspector::properties('\\lithium\\core\\Foo')); }
protected static function _extractFileCode($ref, $options) { $library = Libraries::get($options['library']); $file = $ref['file']; if (!($path = realpath("{$library['path']}/{$file}"))) { return; } list($start, $end) = array_map('intval', explode('-', $ref['lines'])); $lines = Inspector::lines(file_get_contents($path), range($start, $end)); return array("{$ref['file']}::{$ref['lines']}", "\t" . join("\n\t", $lines)); }
protected static function _class(array $object, array $data, array $options = array()) { $identifier = $object['identifier']; $proto = array('parent' => get_parent_class($identifier), 'methods' => Inspector::methods($identifier, null, array('public' => false)), 'properties' => Inspector::properties($identifier, array('public' => false))); $classes = Libraries::find($object['library'], array('recursive' => true)); $proto['subClasses'] = array_filter($classes, function ($class) use($identifier) { if (preg_match('/\\\\(libraries)\\\\|\\\\(mocks)\\\\|\\\\(tests)\\\\/', $class)) { return false; } try { return get_parent_class($class) == $identifier; } catch (Exception $e) { return false; } }); sort($proto['subClasses']); return $proto + $object + array('tags' => isset($data['tags']) ? $data['tags'] : array()); }
public function lines($data, $start, $end) { return Inspector::lines($data, range($start, $end)); }
public function testCallableVisibility() { $obj = new MockMethodFiltering(); $this->assertTrue(Inspector::isCallable($obj, 'method', 0)); $this->assertTrue(Inspector::isCallable($obj, 'method', 1)); $this->assertFalse(Inspector::isCallable('lithium\\action\\Dispatcher', '_callable', 0)); $this->assertTrue(Inspector::isCallable('lithium\\action\\Dispatcher', '_callable', 1)); }
/** * Will determine if a method can be called. * * @param string $method Method name. * @param bool $internal Interal call or not. * @return bool */ public function respondsTo($method, $internal = false) { return Inspector::isCallable($this, $method, $internal); }
/** * Convert an exception object to an exception result array for test reporting. * * @param array $exception The exception data to report on. Statistics are gathered and * added to the reporting stack contained in `Unit::$_results`. * @param string $lineFlag * @return void * @todo Refactor so that reporters handle trace formatting. */ protected function _reportException($exception, $lineFlag = null) { $message = $exception['message']; $isExpected = ($exp = end($this->_expected)) && ($exp === true || $exp === $message || Validator::isRegex($exp) && preg_match($exp, $message)); if ($isExpected) { return array_pop($this->_expected); } $initFrame = current($exception['trace']) + array('class' => '-', 'function' => '-'); foreach ($exception['trace'] as $frame) { if (isset($scopedFrame)) { break; } if (!class_exists('lithium\\analysis\\Inspector')) { continue; } if (isset($frame['class']) && in_array($frame['class'], Inspector::parents($this))) { $scopedFrame = $frame; } } if (class_exists('lithium\\analysis\\Debugger')) { $exception['trace'] = Debugger::trace(array('trace' => $exception['trace'], 'format' => '{:functionRef}, line {:line}', 'includeScope' => false, 'scope' => array_filter(array('functionRef' => __NAMESPACE__ . '\\{closure}', 'line' => $lineFlag)))); } $this->_result('exception', $exception + array('class' => $initFrame['class'], 'method' => $initFrame['function'])); }
/** * Tests getting static and non-static properties from various types of classes. * * @return void */ public function testGetClassProperties() { $result = array_map(function ($property) { return $property['name']; }, Inspector::properties(__CLASS__)); $expected = array('test', 'test2'); $this->assertEqual($expected, $result); $result = array_map(function ($property) { return $property['name']; }, Inspector::properties(__CLASS__, array('public' => false))); $expected = array('test', 'test2', '_test'); $this->assertEqual($expected, $result); $result = Inspector::properties(__CLASS__); $expected = array(array('modifiers' => array('public'), 'docComment' => false, 'name' => 'test', 'value' => null), array('modifiers' => array('public', 'static'), 'docComment' => false, 'name' => 'test2', 'value' => 'bar')); $this->assertEqual($expected, $result); }