Skip to content

Instantly share code, notes, and snippets.

@satooshi
Created April 12, 2013 16:08
Show Gist options
  • Save satooshi/5373125 to your computer and use it in GitHub Desktop.
Save satooshi/5373125 to your computer and use it in GitHub Desktop.
I wrote PHP library for Coveralls. https://github.com/satooshi/php-coveralls PHP script for Coveralls. clover.xml generated by PHPUnit is required to get coverage data. And repo_token for Coveralls repository is required for API access.
<?php
// data
/**
* Data represents "source_files" element of Coveralls' "json_file".
*
* @author Kitamura Satoshi <[email protected]>
*/
class SourceFile
{
/**
* Source filename.
*
* @var string
*/
protected $name;
/**
* Source content.
*
* @var string
*/
protected $source;
/**
* Coverage data of the source file.
*
* @var array
*/
protected $coverage;
/**
* Absolute path.
*
* @var string
*/
protected $path;
/**
* Line number of the source file.
*
* @var integer
*/
protected $fileLines;
/**
* Constructor.
*
* @param string $path Absolute path.
* @param string $name Source filename.
* @param string $eol End of line.
*/
public function __construct($path, $name, $eol = "\n")
{
$this->path = $path;
$this->name = $name;
$this->source = file_get_contents($path);
$lines = explode($eol, $this->source);
$this->fileLines = count($lines);
$this->coverage = array_fill(0, $this->fileLines, null);
}
/**
* String expression (convert to json).
*
* @return string
*/
public function __toString()
{
return json_encode($this->toArray());
}
/**
* Convert to an array.
*
* @return array
*/
public function toArray()
{
return array(
'name' => $this->name,
'source' => $this->source,
'coverage' => $this->coverage,
);
}
// API
/**
* Add coverage.
*
* @param integer $lineNum Line number.
* @param integer $count Number of covered.
* @return void
*/
public function addCoverage($lineNum, $count)
{
$this->coverage[$lineNum] = $count;
}
// accessor
/**
* Return source filename.
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Return source content.
*
* @return string
*/
public function getSource()
{
return $this->source;
}
/**
* Return coverage data of the source file.
*
* @return array
*/
public function getCoverage()
{
return $this->coverage;
}
/**
* Return absolute path.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Return line number of the source file.
*
* @return integer
*/
public function getFileLines()
{
return $this->fileLines;
}
}
/**
* Data represents "json_file" of Coveralls API.
*
* @author Kitamura Satoshi <[email protected]>
*/
class Coveralls
{
/**
* Service name.
*
* @var string
*/
protected $serviceName;
/**
* Repository token.
*
* @var string
*/
protected $repoToken;
/**
* Source files.
*
* @var SourceFile[]
*/
protected $sourceFiles = array();
/**
* Constructor.
*
* @param string $serviceName Service name.
* @param string $repoToken Repo token.
*/
public function __construct($serviceName = 'travis-ci', $repoToken = null)
{
$this->serviceName = $serviceName;
$this->repoToken = $repoToken;
}
/**
* String expression (convert to json).
*
* @return string
*/
public function __toString()
{
return json_encode($this->toArray());
}
/**
* Convert to an array.
*
* @return array
*/
public function toArray()
{
$files = array();
foreach ($this->sourceFiles as $file) {
$files[] = $file->toArray();
}
$data = array(
'service_name' => $this->serviceName,
'source_files' => $files,
);
if ($this->repoToken !== null) {
$data['repo_token'] = $this->repoToken;
}
return $data;
}
// API
/**
* Add SourceFile.
*
* @param SourceFile $sourceFile
*/
public function addSourceFile(SourceFile $sourceFile)
{
$this->sourceFiles[] = $sourceFile;
}
/**
* Return whether the SourceFile object exists.
*
* @return boolean
*/
public function hasSourceFiles()
{
return count($this->sourceFiles) > 0;
}
// accessor
/**
* Set service name.
*
* @param string $serviceName Service name.
* @return Coveralls
*/
public function setServiceName($serviceName)
{
$this->serviceName = $serviceName;
return $this;
}
/**
* Return service name.
*
* @return string.
*/
public function getServiceName()
{
return $this->serviceName;
}
/**
* Set repository token.
*
* @param string $repoToken Repository token.
* @return Coveralls
*/
public function setRepoToken($repoToken)
{
$this->repoToken = $repoToken;
return $this;
}
/**
* Return repository token.
*
* @return string
*/
public function getRepoToken()
{
return $this->repoToken;
}
/**
* Return source files.
*
* @return SourceFile[]
*/
public function getSourceFiles()
{
return $this->sourceFiles;
}
}
// collector
/**
* Coverage collector for clover.xml.
*
* @author Kitamura Satoshi <[email protected]>
*/
class CloverXmlCoverageCollector
{
// API
/**
* Collect coverage from XML object.
*
* @param SimpleXMLElement $xml Clover XML object.
* @param string $root Path to src directory.
* @return Coveralls
*/
public function collect(SimpleXMLElement $xml, $root)
{
$coveralls = new Coveralls();
foreach ($xml->project->package as $package) {
foreach ($package->file as $file) {
$srcFile = $this->collectFileCoverage($file, $root);
if ($srcFile !== null) {
$coveralls->addSourceFile($srcFile);
}
}
}
return $coveralls;
}
// Internal method
/**
* Collect coverage data of a file.
*
* @param SimpleXMLElement $file Clover XML object of a file.
* @param string $root Path to src directory.
* @return SourceFile
*/
protected function collectFileCoverage(SimpleXMLElement $file, $root)
{
$fullpath = (string)$file['name'];
if (false === $pos = strpos($fullpath, $root)) {
return null;
}
$filename = str_replace($root, '', $fullpath);
return $this->collectCoverage($file, $fullpath, $filename);
}
/**
* Collect coverage data.
*
* @param SimpleXMLElement $file Clover XML object of a file.
* @param string $path Path to source file.
* @param string $filename Filename.
* @return SourceFile
*/
protected function collectCoverage(SimpleXMLElement $file, $path, $filename)
{
$srcFile = new SourceFile($path, $filename);
foreach ($file->line as $line) {
$lineNum = (int)$line['num'];
if ($lineNum > 0) {
$srcFile->addCoverage($lineNum - 1, (int)$line['count']);
}
}
return $srcFile;
}
}
// rest client
class RestClient
{
public function upload($url, $path, $filename)
{
$post = array(
$filename => '@' . $path,
);
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $post,
CURLOPT_RETURNTRANSFER => true,
)
);
$data = curl_exec($curl);
$info = curl_getinfo($curl);
if ($info['http_code'] != 200) {
$message = sprintf('Failed to call API. status code: %d', $info['http_code']);
throw new \RuntimeException($message);
}
curl_close($curl);
return $data;
}
}
// run
$xmlFilename = "clover.xml";
$root = realpath(__DIR__ . "/..");
$logs = realpath("$root/build/logs");
$path = realpath("$logs/$xmlFilename");
$src = "$root/src/";
if ($path === false || !file_exists($path)) {
die("Not found $xmlFilename");
}
// collect coverage
$xml = simplexml_load_file($path);
$collector = new CloverXmlCoverageCollector();
$coveralls = $collector->collect($xml, $src);
// set token
$token = 'your-token-here';
$coveralls->setRepoToken($token);
// dump
$coverallsJson = "$logs/coveralls.json";
file_put_contents($coverallsJson, $coveralls);
// upload file
$url = 'https://coveralls.io/api/v1/jobs';
$client = new RestClient();
$data = $client->upload($url, $coverallsJson, 'json_file');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment