composer update

This commit is contained in:
Manish Verma
2018-12-05 10:50:52 +05:30
parent 9eabcacfa7
commit 4addd1e9c6
3328 changed files with 156676 additions and 138988 deletions

View File

@@ -0,0 +1,23 @@
<?php
namespace Codacy\Coverage;
use Symfony\Component\Console\Application as ConsoleApplication;
use Codacy\Coverage\Command\Clover as CloverCommand;
use Codacy\Coverage\Command\Phpunit as PhpunitCommand;
/**
* Class Application
*
*/
class Application extends ConsoleApplication
{
public function __construct()
{
parent::__construct("Codacy Coverage API Client");
$this->add(new CloverCommand());
$this->add(new PhpunitCommand());
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace Codacy\Coverage\Command;
use Symfony\Component\Console\Command\Command as ConsoleCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Codacy\Coverage\Parser\CloverParser;
use Codacy\Coverage\Util\JsonProducer;
use Codacy\Coverage\Util\GitClient;
use Codacy\Coverage\Util\CodacyApiClient;
/**
* Class Clover
*
*/
class Clover extends ConsoleCommand
{
protected function configure()
{
$this
->setName("clover")
->setDescription("Send coverage results in clover format")
->addArgument(
"path_to_coverage_results",
InputArgument::OPTIONAL,
"Path where coverage results are saved: XML file for clover format, directory containing the index.xml for phpunit format"
)
->addOption(
"git-commit",
null,
InputOption::VALUE_REQUIRED,
"Commit hash of results to be send"
)
->addOption(
"base-url",
null,
InputOption::VALUE_REQUIRED,
"Codacy base url"
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$projectToken = $this->getProjectToken();
$parser = $this->getParser($input->getArgument("path_to_coverage_results"));
$jsonProducer = new JsonProducer();
$jsonProducer->setParser($parser);
$commit = $this->getCommitHash($input->getOption("git-commit"));
$baseUrl = $this->getBaseCodacyUrl($input->getOption("base-url"));
$data = $jsonProducer->makeJson();
if ($output->isVerbose()) {
$output->writeln("Sending coverage results to " . $baseUrl);
$output->writeln("Generated JSON:");
$output->writeln($data);
}
$client = new CodacyApiClient($baseUrl, $projectToken);
$result = $client->sendCoverage($commit, $data);
if ($output->isVerbose()) {
$output->writeln($result);
}
}
/**
* Get parser of current format type.
*
* @param string $path Path to clover.xml
*
* @return CloverParser
*/
protected function getParser($path = null)
{
$path = is_null($path) ?
join(DIRECTORY_SEPARATOR, array('build', 'logs', 'clover.xml')) :
$path;
return new CloverParser($path);
}
/**
* Return Codacy Project Token.
*
* @return string Project token
*
* @throws \InvalidArgumentException If Token not specified
*/
protected function getProjectToken()
{
$projectToken = getenv("CODACY_PROJECT_TOKEN");
if ($projectToken == false) {
throw new \InvalidArgumentException(
"Cannot continue with execution as long as your project token is not set as an environmental variable."
. PHP_EOL . "Please type: export CODACY_PROJECT_TOKEN=<YOUR TOKEN>"
);
}
return urlencode($projectToken);
}
/**
* Get Git commit hash of project
*
* @param string $hash Specified hash
*
* @return string Git commit hash
*
* @throws \InvalidArgumentException When bad hash specified, or can't get commit hash
*/
protected function getCommitHash($hash = null)
{
if (!$hash) {
$gClient = new GitClient(getcwd());
return $gClient->getHashOfLatestCommit();
}
if (strlen($hash) != 40) {
throw new \InvalidArgumentException(
sprintf("Invalid git commit hash %s specified", $hash)
);
}
return urlencode($hash);
}
/**
* Return base Codacy Project URL
*
* @param string $url HTTP URL for codacy
*
* @return string Base Codacy Project URL
*/
protected function getBaseCodacyUrl($url = null)
{
if ($url) {
return $url;
}
$url = getenv("CODACY_API_BASE_URL");
return $url ? $url : "https://api.codacy.com";
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Codacy\Coverage\Command;
use Codacy\Coverage\Parser\PhpUnitXmlParser;
/**
* Class Phpunit
*
*/
class Phpunit extends Clover {
protected function configure()
{
parent::configure();
$this
->setName("phpunit")
->setDescription("Send coverage results in phpunit format");
}
protected function getParser($path = null)
{
$path = is_null($path) ?
"build" . DIRECTORY_SEPARATOR . "coverage-xml" :
$path;
$parser = new PhpUnitXmlParser($path . DIRECTORY_SEPARATOR . "index.xml");
$parser->setDirOfFileXmls($path);
return $parser;
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace Codacy\Coverage\Parser;
use Codacy\Coverage\Report\CoverageReport;
use Codacy\Coverage\Report\FileReport;
/**
* Parses Clover XML file and produces a CoverageReport object.
* Inherits constructor from abstract class Parser and implements
* the IParser interface.
* @author Jakob Pupke <jakob.pupke@gmail.com>
*/
class CloverParser extends XMLParser implements IParser
{
/**
* Extracts basic information about coverage report and delegates
* more detailed extraction work to _makeFileReports() method.
* @return CoverageReport $report The CoverageReport object
*/
public function makeReport()
{
$project = $this->element->project;
$projectMetrics = $project->metrics;
$coveredStatements = intval($projectMetrics['coveredstatements']);
$statementsTotal = intval($projectMetrics['statements']);
$reportTotal = round($this->safeDivision($coveredStatements, $statementsTotal) * 100);
$fileReports = $this->makeFileReports($project);
$report = new CoverageReport($reportTotal, $fileReports);
return $report;
}
/**
* Takes the root \SimpleXMLElement object of the parsed file
* and decides on how to iterate it to extract information of all
* <file...>..</file> nodes.
* @param \SimpleXMLElement $node the root XML node.
* @return array holding FileReport objects
*/
private function makeFileReports(\SimpleXMLElement $node)
{
$fileReports = array();
/*
* Most clover reports will have project/package/file/line xPath.
* But there could be files that are not part of any package, i.e files that
* that do not declare namespace.
*/
if ($node->file->count() > 0) {
// so there is a file without package
$fileReports = $this->makeFileReportsFromFiles($node->file, $fileReports);
}
if ($node->package->count() > 0) {
$fileReports = $this->makeFileReportsFromPackages($node->package, $fileReports);
}
return $fileReports;
}
/**
* Iterates all over all <file...>..</file> nodes.
* @param \SimpleXMLElement $node The XML node holding the file nodes.
* @param array $fileReports array of FileReport objects
* @return array holding FileReport objects
*/
private function makeFileReportsFromFiles(\SimpleXMLElement $node, $fileReports)
{
foreach ($node as $file) {
// iterate files in the package
$countStatement = intval($file->metrics['statements']);
$countCoveredStatements = intval($file->metrics['coveredstatements']);
if ($countStatement == 0) {
$fileTotal = 0;
} else {
$fileTotal = round($this->safeDivision($countCoveredStatements, $countStatement) * 100);
}
$fileName = $this->getRelativePath($file['name']);
$lineCoverage = $this->getLineCoverage($file);
$fileReport = new FileReport($fileTotal, $fileName, $lineCoverage);
array_push($fileReports, $fileReport);
}
return $fileReports;
}
/**
* Iterates over all <package..>...</package> nodes and calls _makeFileReportsFromFiles on them
* @param \SimpleXMLElement $node The XML node holding all <package..>...</package> nodes
* @param array $fileReports array of FileReport objects
* @return array holding FileReport objects
*/
private function makeFileReportsFromPackages(\SimpleXMLElement $node, $fileReports)
{
// iterate all packages
foreach ($node as $package) {
$fileReports = $this->makeFileReportsFromFiles($package->file, $fileReports);
}
return $fileReports;
}
/**
* Iterates all <line></line> nodes and produces an array holding line coverage information.
* Only adds lines of type "stmt" and with count greater than 0.
* @param \SimpleXMLElement $node The XML node holding the <line></line> nodes
* @return array: (lineNumber -> hits)
*/
private function getLineCoverage(\SimpleXMLElement $node)
{
$lineCoverage = (object)array();
foreach ($node as $line) {
$count = intval($line['count']);
// iterate all lines in that file
if ($line['type'] == 'stmt') {
$lineNr = (string)$line['num'];
$hit = $count;
$lineCoverage->$lineNr = $hit;
}
}
return $lineCoverage;
}
/**
* Cuts the file name so we have relative path to projectRoot.
* In a clover file file names are saved from / on.
* We are only interested in relative filename
* @param \SimpleXMLElement $fileName The filename attribute
* @return string The relative path of that file
*/
private function getRelativePath(\SimpleXMLElement $fileName)
{
$prefix = $this->rootDir . DIRECTORY_SEPARATOR;
$str = (string)$fileName;
if (substr($str, 0, strlen($prefix)) == $prefix) {
$str = substr($str, strlen($prefix));
}
return $str;
}
private function safeDivision($a, $b)
{
if ($b === 0) {
return 0;
}
return $a / $b;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Codacy\Coverage\Parser;
/**
* Interface IParser
* All parsers need to implement this interface. This allows the JsonProducer
* to be composed of different parsers via JsonProducer::setParser($parser).
* @author Jakob Pupke <jakob.pupke@gmail.com>
*/
interface IParser
{
public function makeReport();
}
/**
* Class XMLParser
* The superclass of all parsers that parse XML files.
* @author Jakob Pupke <jakob.pupke@gmail.com>
*/
abstract class XMLParser
{
/**
* @var \SimpleXMLElement
*/
protected $element;
/**
* @var null|string The root directory. Can be other than current working directory
* in order to make tests pass against the static reports in tests/res directory.
*/
protected $rootDir;
/**
* Construct PhpUnitXmlParser and set the XML object as member field.
* All XML parser classes inherit this constructor.
* @param string $rootDir Is only for making tests pass.
* @param string $path Path to XML file
*/
public function __construct($path, $rootDir = null)
{
if (file_exists($path)) {
if ($rootDir == null) {
$this->rootDir = getcwd();
} else {
$this->rootDir = $rootDir;
}
$this->element = simplexml_load_file($path);
} else {
throw new \InvalidArgumentException(
"Unable to load the xml file. Make sure path is properly set. " .
"Using: \"$path\"", E_USER_ERROR
);
}
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Codacy\Coverage\Parser;
use Codacy\Coverage\Report\CoverageReport;
use Codacy\Coverage\Report\FileReport;
/**
* Parses XML file, result of phpunit --coverage-xml, and produces
* a CoverageReport object. The challenging problem here is that
* the report is scattered over different files. Basic information
* can be parsed from the index.xml file. But the relevant information
* for each file is stored in individual files.
* @author Jakob Pupke <jakob.pupke@gmail.com>
*/
class PhpUnitXmlParser extends XMLParser implements IParser
{
protected $dirOfFileXmls;
/**
* Extracts basic information about coverage report
* from the root xml file (index.xml).
* For line coverage information about the files it has
* to parse each individual file. This is handled by
* _getLineCoverage() private method.
* @return CoverageReport $report The CoverageReport object
*/
/**
* @param $dir string The path to where the single file xmls reside
*/
public function setDirOfFileXmls($dir)
{
$this->dirOfFileXmls = $dir;
}
/**
* @return string The path to where the single file xmls reside
*/
public function getDirOfFileXmls()
{
return $this->dirOfFileXmls;
}
public function makeReport()
{
//we can get the report total from the first directory summary.
$reportTotal = $this->getTotalFromPercent($this->element->project->directory->totals->lines["percent"]);
$fileReports = array();
foreach ($this->element->project->directory->file as $file) {
$fileName = $this->getRelativePath($file["href"]);
$fileTotal = $this->getTotalFromPercent($file->totals->lines["percent"]);
$xmlFileHref = (string)$file["href"];
$base = $this->getDirOfFileXmls();
// get the corresponding xml file to get lineCoverage information.
if (file_exists($base . DIRECTORY_SEPARATOR . $xmlFileHref)) {
$fileXml = simplexml_load_file($base . DIRECTORY_SEPARATOR . $xmlFileHref);
} else {
throw new \InvalidArgumentException(
"Error: Cannot read XML file. Using: " . $base . DIRECTORY_SEPARATOR . $xmlFileHref . "\n\r"
);
}
$lineCoverage = $this->getLineCoverage($fileXml);
$fileReport = new FileReport($fileTotal, $fileName, $lineCoverage);
array_push($fileReports, $fileReport);
}
$report = new CoverageReport($reportTotal, $fileReports);
return $report;
}
/**
* Iterates all <line></line> nodes and produces an array holding line coverage information.
* @param \SimpleXMLElement $node The XML node holding the <line></line> nodes
* @return array: (lineNumber -> hits)
*/
private function getLineCoverage(\SimpleXMLElement $node)
{
$lineCoverage = (object)array();
if ($node->file->coverage) {
foreach ($node->file->coverage->line as $line) {
$count = $line->covered->count();
$nr = (string)$line["nr"];
$lineCoverage->$nr = $count;
}
}
return $lineCoverage;
}
/**
* Gets Integer from percent. Example: 95.00% -> 95
* @param \SimpleXMLElement $percent The percent attribute of the node
* @return integer The according integer value
*/
private function getTotalFromPercent(\SimpleXMLElement $percent)
{
$percent = (string)$percent;
$percent = substr($percent, 0, -1);
return round($percent);
}
/**
* The PhpUnit XML Coverage format does not save the full path of the filename
* We can get the filename by combining the path of the first directory with
* the href attribute of each file.
* @param \SimpleXMLElement $fileName The href attribute of the <file></file> node.
* @return string The relative path of the file, that is, relative to project root.
*/
private function getRelativePath(\SimpleXMLElement $fileName)
{
$dirOfSrcFiles = $this->element->project->directory["name"];
$projectRoot = $this->rootDir;
// Need to cut off everything lower than projectRoot
$dirFromProjectRoot = substr($dirOfSrcFiles, strlen($projectRoot) + 1);
// remove .xml and convert to string
$relativeFilePath = substr((string)$fileName, 0, -4);
return join(DIRECTORY_SEPARATOR, array($dirFromProjectRoot, $relativeFilePath));
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Codacy\Coverage\Report;
/**
* Class CoverageReport
* Holds the coverage report total result and a list (array)
* of FileReports
* @author Jakob Pupke <jakob.pupke@gmail.com>
*/
class CoverageReport
{
/**
* @var integer
*/
private $_total;
/**
* @var array (of type FileReport)
*/
private $_fileReports;
/**
* @param $total string
* @param $fileReports array (of type FileReport)
*/
public function __construct($total, $fileReports)
{
$this->_total = $total;
$this->_fileReports = $fileReports;
}
/**
* @return integer
*/
public function getTotal()
{
return $this->_total;
}
/**
* @return array (of type FileReport)
*/
public function getFileReports()
{
return $this->_fileReports;
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Codacy\Coverage\Report;
/**
* Class FileReport
* Holds the file report total result, the filename and a list (associative array)
* mapping line numbers to hits [lineNr => hits]
* @author Jakob Pupke <jakob.pupke@gmail.com>
*/
class FileReport
{
/**
* @var integer
*/
private $_total;
/**
* @var string
*/
private $_fileName;
/**
* @var array (line -> hits) of type [string -> int]
*/
private $_lineCoverage;
/**
* @param $total string
* @param $fileName string
* @param $lineCoverage array
*/
public function __construct($total, $fileName, $lineCoverage)
{
$this->_total = $total;
$this->_fileName = $fileName;
$this->_lineCoverage = $lineCoverage;
}
/**
* @return integer
*/
public function getTotal()
{
return $this->_total;
}
/**
* @return string
*/
public function getFileName()
{
return $this->_fileName;
}
/**
* @return array
*/
public function getLineCoverage()
{
return $this->_lineCoverage;
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Codacy\Coverage\Util;
/**
* Class ApiClient
* @author Jakob Pupke <jakob.pupke@gmail.com>
*/
class CodacyApiClient
{
function __construct($baseUrl, $projectToken)
{
$this->baseUrl = $baseUrl;
$this->projectToken = $projectToken;
}
/**
* @param string $commit commit uuid
* @param string $data the JSON data
*
* @return string success message
*
* @throws \Exception when remote server response
*/
public function sendCoverage($commit, $data)
{
$tempCertFile = $this->dumpCertificateBundle();
$url = $this->baseUrl . "/2.0/coverage/" . $commit . "/php";
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt(
$curl, CURLOPT_HTTPHEADER,
array(
"Content-type: application/json",
"project_token: " . $this->projectToken
)
);
curl_setopt($curl, CURLOPT_CAINFO, $tempCertFile);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$json_response = curl_exec($curl);
unlink($tempCertFile);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($status < 200 || $status > 201) {
throw new \Exception(
sprintf("Error: call to URL %s failed with status %s, response %s, curl_error %u",
$url, $status, $json_response, curl_error($curl), curl_errno($curl)
)
);
}
curl_close($curl);
$json = json_decode($json_response, true);
if (isset($json['success']) || array_key_exists('success', $json)) {
return $json['success'];
} else {
return $json['error'];
}
}
/**
* Store certificate bundle to temporary file to be available when used within phar context
*
* @return string Full qualified path to temporary file
*/
protected function dumpCertificateBundle()
{
$tempCertFile = tempnam(sys_get_temp_dir(), 'cacert');
copy(dirname(__FILE__) . '/cacert.pem', $tempCertFile);
return $tempCertFile;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Codacy\Coverage\Util;
use Gitonomy\Git\Repository;
use Codacy\Coverage\Util\Config;
/**
* Class GitClient
* @author Jakob Pupke <jakob.pupke@gmail.com>
*/
class GitClient
{
/**
* @var Repository
*/
private $_repository;
/**
* Instantiates a GitClient object. Reads conf.ini to get the path to the repository.
* Throws InvalidArgumentException is projectRoot is not properly set in ini file.
*/
public function __construct($path)
{
if (is_dir(getcwd())) {
$this->_repository = new Repository($path);
} else {
throw new \InvalidArgumentException(
"Could not instantiate GitClient. Using: "
. getcwd()
);
}
}
/**
* @return string The Hash of the latest Commit.
*/
public function getHashOfLatestCommit()
{
$head = $this->_repository->getHeadCommit();
return $head->getHash();
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace Codacy\Coverage\Util;
use Codacy\Coverage\Parser\IParser;
/**
* Class JsonProducer
* Is composed of a parser that implements the IParser interface.
* @author Jakob Pupke <jakob.pupke@gmail.com>
*/
class JsonProducer
{
/**
* @var Parser that implements IParser interface
*/
private $_parser;
/**
* Sets the JsonParser's member field
* @param $parser IParser Any parser class that implements the IParser interface
*/
public function setParser(IParser $parser)
{
$this->_parser = $parser;
}
/**
* Delegates the job to the parser's makeReport() method
* @return CoverageReport The CoverageReport object
*/
public function makeReport()
{
return $this->_parser->makeReport();
}
/**
* Takes a CoverageReport object, the result of makeReport(), and outputs JSON.
* Example JSON format:
* {
* "total": 67,
* "fileReports": [
* {
* "filename": "src/Codacy/Coverage/Api/Api.php",
* "total": 3,
* "coverage": {
* "12": 3,
* "13": 5,
* .........
* .........
* }
* },
* .........
* .......
* ]
* }
*
* @return string the JSON string
*/
public function makeJson()
{
$report = $this->makeReport();
$array = array();
$array['total'] = $report->getTotal();
$fileReportsArray = array();
$fileReports = $report->getFileReports();
foreach ($fileReports as $fr) {
$fileArray = array();
$fileArray['filename'] = $fr->getFileName();
$fileArray['total'] = $fr->getTotal();
$fileArray['coverage'] = $fr->getLineCoverage();
array_push($fileReportsArray, $fileArray);
}
$array['fileReports'] = $fileReportsArray;
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($array, JSON_UNESCAPED_SLASHES);
} else {
return str_replace('\/', '/', json_encode($array));
}
}
}

File diff suppressed because it is too large Load Diff