Browse Source

Code formatting

tags/v13.1.0
Cédric Belin 1 month ago
parent
commit
1133ba0016
55 changed files with 2022 additions and 2015 deletions
  1. +6
    -3
      .editorconfig
  2. +1
    -1
      .github/workflows/build.yaml
  3. +5
    -5
      .vscode/settings.json
  4. +2
    -2
      bin/coveralls
  5. +63
    -63
      composer.json
  6. +1
    -1
      doc/features.md
  7. +1
    -1
      doc/index.md
  8. +5
    -5
      doc/installation.md
  9. +9
    -9
      doc/usage/api.md
  10. +14
    -14
      doc/usage/cli.md
  11. +9
    -5
      etc/mkdocs.yaml
  12. +22
    -22
      etc/phpdoc.xml
  13. +2
    -2
      etc/phpstan.neon
  14. +13
    -13
      etc/phpunit.xml
  15. +9
    -9
      example/main.php
  16. +24
    -24
      src/Cli/Command.php
  17. +1
    -1
      src/Cli/version.g.php
  18. +128
    -128
      src/Client.php
  19. +19
    -19
      src/ClientException.php
  20. +194
    -194
      src/Configuration.php
  21. +143
    -143
      src/GitCommit.php
  22. +98
    -98
      src/GitData.php
  23. +49
    -49
      src/GitRemote.php
  24. +269
    -269
      src/Job.php
  25. +38
    -38
      src/Parsers/Clover.php
  26. +42
    -42
      src/Parsers/Lcov.php
  27. +16
    -16
      src/RequestEvent.php
  28. +18
    -18
      src/ResponseEvent.php
  29. +21
    -21
      src/Services/AppVeyor.php
  30. +17
    -17
      src/Services/CircleCI.php
  31. +15
    -15
      src/Services/Codeship.php
  32. +46
    -46
      src/Services/GitHub.php
  33. +14
    -14
      src/Services/GitLabCI.php
  34. +16
    -16
      src/Services/Jenkins.php
  35. +14
    -14
      src/Services/Semaphore.php
  36. +16
    -16
      src/Services/SolanoCI.php
  37. +12
    -12
      src/Services/Surf.php
  38. +19
    -19
      src/Services/TravisCI.php
  39. +13
    -13
      src/Services/Wercker.php
  40. +87
    -87
      src/SourceFile.php
  41. +12
    -12
      test/ClientTest.php
  42. +142
    -142
      test/ConfigurationTest.php
  43. +42
    -42
      test/GitCommitTest.php
  44. +63
    -63
      test/GitDataTest.php
  45. +33
    -33
      test/GitRemoteTest.php
  46. +55
    -55
      test/JobTest.php
  47. +38
    -38
      test/Parsers/CloverTest.php
  48. +37
    -37
      test/Parsers/LcovTest.php
  49. +51
    -51
      test/SourceFileTest.php
  50. +47
    -47
      test/fixtures/clover.xml
  51. +3
    -3
      tool/build.ps1
  52. +3
    -3
      tool/clean.ps1
  53. +1
    -1
      tool/doc.ps1
  54. +1
    -1
      tool/upgrade.ps1
  55. +3
    -3
      tool/watch.ps1

+ 6
- 3
.editorconfig View File

@@ -3,11 +3,14 @@ root = true

[*]
charset = utf-8
indent_size = 2
indent_style = space
indent_style = tab
insert_final_newline = true
tab_width = 2
trim_trailing_whitespace = true

[*.md]
indent_size = 4
trim_trailing_whitespace = false

[*.{yaml,yml}]
indent_size = 2
indent_style = space

+ 1
- 1
.github/workflows/build.yaml View File

@@ -3,7 +3,7 @@ on:
pull_request:
push:
schedule:
- cron: '0 0 1 * *'
- cron: "0 0 1 * *"
jobs:
test:
runs-on: ubuntu-latest


+ 5
- 5
.vscode/settings.json View File

@@ -1,7 +1,7 @@
{
"editor.insertSpaces": true,
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true
"editor.insertSpaces": false,
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true
}

+ 2
- 2
bin/coveralls View File

@@ -5,12 +5,12 @@ use Coveralls\Cli\{Command};
use Symfony\Component\Console\{Application};

// Load the dependencies.
$autoloader = new SplFileInfo(__DIR__.'/../../../autoload.php');
$autoloader = new SplFileInfo(__DIR__."/../../../autoload.php");
$rootPath = (new SplFileInfo(__DIR__))->getPath();
require_once $autoloader->isFile() ? $autoloader->getPathname() : "$rootPath/vendor/autoload.php";

// Start the application.
$application = new Application('Coveralls.php', require "$rootPath/src/Cli/version.g.php");
$application = new Application("Coveralls.php", require "$rootPath/src/Cli/version.g.php");
$command = (new Command)->setProcessTitle($application->getName());
$application->add($command);
$application->setDefaultCommand($command->getName(), true)->run();

+ 63
- 63
composer.json View File

@@ -1,65 +1,65 @@
{
"description": "Send Clover and LCOV coverage reports to the Coveralls service.",
"homepage": "https://docs.belin.io/coveralls.php",
"license": "MIT",
"name": "cedx/coveralls",
"type": "library",
"version": "13.0.0",
"authors": [
{"email": "cedric@belin.io", "homepage": "https://belin.io", "name": "Cédric Belin"}
],
"autoload": {
"psr-4": {"Coveralls\\": "src/"}
},
"autoload-dev": {
"psr-4": {"Coveralls\\": "test/"}
},
"bin": [
"bin/coveralls"
],
"config": {
"optimize-autoloader": true
},
"funding": [
{"type": "patreon", "url": "https://www.patreon.com/cedx"}
],
"keywords": [
"client",
"clover",
"code",
"coverage",
"coveralls",
"lcov"
],
"require": {
"php": ">=7.4.0",
"ext-date": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pcre": "*",
"ext-SimpleXML": "*",
"ext-spl": "*",
"cedx/lcov": "^8.2.0",
"cedx/which": "^9.0.0",
"nyholm/psr7": "^1.2.1",
"psr/http-client": "^1.0.0",
"symfony/console": "^5.0.8",
"symfony/event-dispatcher": "^5.0.8",
"symfony/http-client": "^5.0.8",
"symfony/mime": "^5.0.8",
"symfony/yaml": "^5.0.8",
"webmozart/path-util": "^2.3.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.25",
"phpunit/phpunit": "^9.1.4"
},
"scripts": {
"coverage": "php bin/coveralls var/coverage.xml",
"test": "phpunit --configuration=etc/phpunit.xml"
},
"support": {
"docs": "https://api.belin.io/coveralls.php",
"issues": "https://git.belin.io/cedx/coveralls.php/issues"
}
"description": "Send Clover and LCOV coverage reports to the Coveralls service.",
"homepage": "https://docs.belin.io/coveralls.php",
"license": "MIT",
"name": "cedx/coveralls",
"type": "library",
"version": "13.0.0",
"authors": [
{"email": "cedric@belin.io", "homepage": "https://belin.io", "name": "Cédric Belin"}
],
"autoload": {
"psr-4": {"Coveralls\\": "src/"}
},
"autoload-dev": {
"psr-4": {"Coveralls\\": "test/"}
},
"bin": [
"bin/coveralls"
],
"config": {
"optimize-autoloader": true
},
"funding": [
{"type": "patreon", "url": "https://www.patreon.com/cedx"}
],
"keywords": [
"client",
"clover",
"code",
"coverage",
"coveralls",
"lcov"
],
"require": {
"php": ">=7.4.0",
"ext-date": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-pcre": "*",
"ext-SimpleXML": "*",
"ext-spl": "*",
"cedx/lcov": "^8.2.0",
"cedx/which": "^9.0.0",
"nyholm/psr7": "^1.2.1",
"psr/http-client": "^1.0.0",
"symfony/console": "^5.0.8",
"symfony/event-dispatcher": "^5.0.8",
"symfony/http-client": "^5.0.8",
"symfony/mime": "^5.0.8",
"symfony/yaml": "^5.0.8",
"webmozart/path-util": "^2.3.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.25",
"phpunit/phpunit": "^9.1.4"
},
"scripts": {
"coverage": "php bin/coveralls var/coverage.xml",
"test": "phpunit --configuration=etc/phpunit.xml"
},
"support": {
"docs": "https://api.belin.io/coveralls.php",
"issues": "https://git.belin.io/cedx/coveralls.php/issues"
}
}

+ 1
- 1
doc/features.md View File

@@ -21,7 +21,7 @@ This project has been tested with [Travis CI](https://travis-ci.com) service, bu
- [Wercker](https://app.wercker.com)

!!! tip
You can find an [example workflow for GitHub Actions](https://git.belin.io/cedx/coveralls.php/src/branch/master/.github/workflows/build.yaml) in the sources of this project.
You can find an [example workflow for GitHub Actions](https://git.belin.io/cedx/coveralls.php/src/branch/master/.github/workflows/build.yaml) in the sources of this project.

## Environment variables
If your build system is not supported, you can still use this package.


+ 1
- 1
doc/index.md View File

@@ -8,7 +8,7 @@ Send [LCOV](http://ltp.sourceforge.net/coverage/lcov.php) and [Clover](https://w
## Quick start
Install the latest version of **Coveralls for PHP** with [Composer](https://getcomposer.org):

```shell
``` shell
composer require cedx/coveralls
```



+ 5
- 5
doc/installation.md View File

@@ -6,7 +6,7 @@ and [Composer](https://getcomposer.org), the PHP package manager, up and running

You can verify if you're already good to go with the following commands:

```shell
``` shell
php --version
# PHP 7.4.5 (cli) (built: Apr 14 2020 16:17:19) ( NTS Visual C++ 2017 x64 )

@@ -15,22 +15,22 @@ composer --version
```

!!! info
If you plan to play with the package sources, you will also need the latest versions of
[PowerShell](https://docs.microsoft.com/en-us/powershell) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).
If you plan to play with the package sources, you will also need the latest versions of
[PowerShell](https://docs.microsoft.com/en-us/powershell) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).

## Installing with Composer package manager

### 1. Install it
From a command prompt, run:

```shell
``` shell
composer require cedx/coveralls
```

### 2. Import it
Now in your [PHP](https://www.php.net) code, you can use:

```php
``` php
<?php
use Coveralls\{Client, ClientException};
```

+ 9
- 9
doc/usage/api.md View File

@@ -6,20 +6,20 @@ source: src/Client.php
# Application programming interface
The hard way. Use the `Coveralls\Client` class to upload your coverage reports:

```php
``` php
<?php
use Coveralls\{Client, ClientException};

function main(): void {
try {
$coverage = file_get_contents('/path/to/coverage.report');
$coverage = file_get_contents("/path/to/coverage.report");
(new Client)->upload($coverage);
echo 'The report was sent successfully.';
echo "The report was sent successfully.";
}

catch (Throwable $e) {
echo 'An error occurred: ', $e->getMessage();
if ($e instanceof ClientException) echo 'From: ', $e->getUri(), PHP_EOL;
echo "An error occurred: ", $e->getMessage();
if ($e instanceof ClientException) echo "From: ", $e->getUri(), PHP_EOL;
}
}
```
@@ -33,14 +33,14 @@ The `Coveralls\Client` class is an [EventDispatcher](https://symfony.com/doc/cur
### The `Client::eventRequest` event
Emitted every time a request is made to the remote service:

```php
``` php
<?php
use Coveralls\{Client, RequestEvent};

function main(): void {
$client = new Client;
$client->addListener(Client::eventRequest, function(RequestEvent $event) {
echo 'Client request: ', $event->getRequest()->getUri();
echo "Client request: ", $event->getRequest()->getUri();
});
}
```
@@ -48,14 +48,14 @@ function main(): void {
### The `Client::eventResponse` event
Emitted every time a response is received from the remote service:

```php
``` php
<?php
use Coveralls\{Client, ResponseEvent};

function main(): void {
$client = new Client;
$client->addListener(Client::eventResponse, function(ResponseEvent $event) {
echo 'Server response: ', $event->getResponse()->getStatusCode();
echo "Server response: ", $event->getResponse()->getStatusCode();
});
}
```

+ 14
- 14
doc/usage/cli.md View File

@@ -6,39 +6,39 @@ source: bin/coveralls
# Command line interface
The easy way. From a command prompt, install the `coveralls` executable:

```shell
``` shell
composer global require cedx/coveralls
```

!!! tip
Consider adding the [`composer global`](https://getcomposer.org/doc/03-cli.md#global) executables directory to your system path.
Consider adding the [`composer global`](https://getcomposer.org/doc/03-cli.md#global) executables directory to your system path.

Then use it to upload your coverage reports:

```shell
``` shell
$ coveralls --help

Description:
Send a coverage report to the Coveralls service.
Send a coverage report to the Coveralls service.

Usage:
coveralls <file>
coveralls <file>

Arguments:
file The path of the coverage report to upload
file The path of the coverage report to upload

Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
```

For example:

```shell
``` shell
coveralls build/coverage.xml
```

+ 9
- 5
etc/mkdocs.yaml View File

@@ -8,19 +8,23 @@ site_dir: ../www

repo_name: git.belin.io
repo_url: https://git.belin.io/cedx/coveralls.php
edit_uri: ''
edit_uri: ""

copyright: Copyright &copy; 2017 - 2020 Cédric Belin
extra:
social:
- icon: fontawesome/solid/globe
link: 'https://belin.io'
link: "https://belin.io"
name: Belin.io
- icon: fontawesome/brands/github
link: 'https://github.com/cedx'
link: "https://github.com/cedx"
name: GitHub
- icon: fontawesome/brands/twitter
link: 'https://twitter.com/cedxbelin'
link: "https://twitter.com/cedxbelin"
name: Twitter
- icon: fontawesome/brands/linkedin
link: 'https://linkedin.com/in/cedxbelin'
link: "https://linkedin.com/in/cedxbelin"
name: LinkedIn

markdown_extensions:
- admonition


+ 22
- 22
etc/phpdoc.xml View File

@@ -1,25 +1,25 @@
<?xml version="1.0"?>
<phpdocumentor
configVersion="3"
xmlns="http://www.phpdoc.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://docs.phpdoc.org/latest/phpdoc.xsd">
<title>Coveralls for PHP</title>
<paths>
<cache>../var/phpdoc</cache>
<output>../doc/api</output>
</paths>
<version number="13.0.0">
<api>
<markers>
<marker>TODO</marker>
</markers>
<source dsn="..">
<path>src</path>
</source>
<visibility>protected</visibility>
<visibility>public</visibility>
</api>
</version>
configVersion="3"
xmlns="http://www.phpdoc.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://docs.phpdoc.org/latest/phpdoc.xsd">
<title>Coveralls for PHP</title>
<paths>
<cache>../var/phpdoc</cache>
<output>../doc/api</output>
</paths>
<version number="13.0.0">
<api>
<markers>
<marker>TODO</marker>
</markers>
<source dsn="..">
<path>src</path>
</source>
<visibility>protected</visibility>
<visibility>public</visibility>
</api>
</version>
</phpdocumentor>

+ 2
- 2
etc/phpstan.neon View File

@@ -1,3 +1,3 @@
parameters:
level: max
paths: [../src, ../test]
level: max
paths: [../src, ../test]

+ 13
- 13
etc/phpunit.xml View File

@@ -1,18 +1,18 @@
<?xml version="1.0"?>
<phpunit bootstrap="../vendor/autoload.php" cacheResult="false" testdox="true">
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../src</directory>
</whitelist>
</filter>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../src</directory>
</whitelist>
</filter>

<logging>
<log type="coverage-clover" target="../var/coverage.xml"/>
</logging>
<logging>
<log type="coverage-clover" target="../var/coverage.xml"/>
</logging>

<testsuites>
<testsuite name="all">
<directory suffix="Test.php">../test</directory>
</testsuite>
</testsuites>
<testsuites>
<testsuite name="all">
<directory suffix="Test.php">../test</directory>
</testsuite>
</testsuites>
</phpunit>

+ 9
- 9
example/main.php View File

@@ -3,14 +3,14 @@ use Coveralls\{Client, ClientException};

/** Uploads a coverage report. */
function main(): void {
try {
$coverage = file_get_contents('/path/to/coverage.report');
(new Client)->upload($coverage);
echo 'The report was sent successfully.';
}
try {
$coverage = file_get_contents("/path/to/coverage.report");
(new Client)->upload($coverage);
echo "The report was sent successfully.";
}

catch (Throwable $e) {
echo 'An error occurred: ', $e->getMessage();
if ($e instanceof ClientException) echo 'From: ', $e->getUri(), PHP_EOL;
}
catch (Throwable $e) {
echo "An error occurred: ", $e->getMessage();
if ($e instanceof ClientException) echo "From: ", $e->getUri(), PHP_EOL;
}
}

+ 24
- 24
src/Cli/Command.php View File

@@ -10,31 +10,31 @@ use Symfony\Component\Console\Output\{OutputInterface};
/** The console command. */
class Command extends \Symfony\Component\Console\Command\Command {

/** @var string The command name. */
protected static $defaultName = 'coveralls';
/** @var string The command name. */
protected static $defaultName = "coveralls";

/** Configures the current command. */
protected function configure(): void {
$this
->setDescription('Send a coverage report to the Coveralls service.')
->addArgument('file', InputArgument::REQUIRED, 'The path of the coverage report to upload');
}
/** Configures the current command. */
protected function configure(): void {
$this
->setDescription("Send a coverage report to the Coveralls service.")
->addArgument("file", InputArgument::REQUIRED, "The path of the coverage report to upload");
}

/**
* Executes the current command.
* @param InputInterface $input The input arguments and options.
* @param OutputInterface $output The console output.
* @return int The exit code.
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
/** @var string $path */
$path = $input->getArgument('file');
$file = new \SplFileObject($path);
if (!$file->isReadable()) throw new RuntimeException("File not found: {$file->getPathname()}");
/**
* Executes the current command.
* @param InputInterface $input The input arguments and options.
* @param OutputInterface $output The console output.
* @return int The exit code.
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
/** @var string $path */
$path = $input->getArgument("file");
$file = new \SplFileObject($path);
if (!$file->isReadable()) throw new RuntimeException("File not found: {$file->getPathname()}");

$client = new Client(new Uri($_SERVER['COVERALLS_ENDPOINT'] ?? Client::defaultEndPoint));
$output->writeln("[Coveralls] Submitting to {$client->getEndPoint()}");
$client->upload((string) $file->fread($file->getSize()));
return 0;
}
$client = new Client(new Uri($_SERVER["COVERALLS_ENDPOINT"] ?? Client::defaultEndPoint));
$output->writeln("[Coveralls] Submitting to {$client->getEndPoint()}");
$client->upload((string) $file->fread($file->getSize()));
return 0;
}
}

+ 1
- 1
src/Cli/version.g.php View File

@@ -1,4 +1,4 @@
<?php declare(strict_types=1);

// The version number of the package.
return $packageVersion = '13.0.0';
return $packageVersion = "13.0.0";

+ 128
- 128
src/Client.php View File

@@ -13,132 +13,132 @@ use function Which\{which};
/** Uploads code coverage reports to the [Coveralls](https://coveralls.io) service. */
class Client extends EventDispatcher {

/** @var string The URL of the default API end point. */
const defaultEndPoint = 'https://coveralls.io/api/v1/';
/** @var string An event that is triggered when a request is made to the remote service. */
const eventRequest = RequestEvent::class;
/** @var string An event that is triggered when a response is received from the remote service. */
const eventResponse = ResponseEvent::class;
/** @var UriInterface The URL of the API end point. */
private UriInterface $endPoint;
/** @var Psr18Client The HTTP client. */
private Psr18Client $http;
/**
* Creates a new client.
* @param UriInterface|null $endPoint The URL of the API end point.
*/
function __construct(?UriInterface $endPoint = null) {
parent::__construct();
$this->http = new Psr18Client;
$this->endPoint = $endPoint ?? $this->http->createUri(static::defaultEndPoint);
}
/**
* Gets the URL of the API end point.
* @return UriInterface The URL of the API end point.
*/
function getEndPoint(): UriInterface {
return $this->endPoint;
}
/**
* Uploads the specified code coverage report to the Coveralls service.
* @param string $coverage A coverage report.
* @param Configuration $config The environment settings.
* @throws \InvalidArgumentException The format of the specified coverage report is not supported.
*/
function upload(string $coverage, Configuration $config = null): void {
assert(mb_strlen($coverage) > 0);
$job = null;
$report = trim($coverage);
if (mb_substr($report, 0, 5) == '<?xml' || mb_substr($report, 0, 9) == '<coverage')
$job = Clover::parseReport($report);
else {
$token = mb_substr($report, 0, 3);
if ($token == 'TN:' || $token == 'SF:') $job = Lcov::parseReport($report);
}
if (!$job) throw new \InvalidArgumentException('The specified coverage format is not supported.');
$this->updateJob($job, $config ?? Configuration::loadDefaults());
if (!$job->getRunAt()) $job->setRunAt(new \DateTimeImmutable);
try {
which('git');
$git = GitData::fromRepository();
$branch = ($gitData = $job->getGit()) ? $gitData->getBranch() : '';
if ($git->getBranch() == 'HEAD' && mb_strlen($branch)) $git->setBranch($branch);
$job->setGit($git);
}
catch (FinderException $e) {}
$this->uploadJob($job);
}
/**
* Uploads the specified job to the Coveralls service.
* @param Job $job The job to be uploaded.
* @throws \InvalidArgumentException The job does not meet the requirements.
* @throws ClientException An error occurred while uploading the report.
*/
function uploadJob(Job $job): void {
if (!$job->getRepoToken() && !$job->getServiceName())
throw new \InvalidArgumentException('The job does not meet the requirements.');
$endPoint = $this->getEndPoint();
$uri = $endPoint->withPath("{$endPoint->getPath()}jobs");
try {
$jsonFile = json_encode($job, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$formData = new FormDataPart(['json_file' => new DataPart($jsonFile, 'coveralls.json')]);
$request = ($this->http->createRequest('POST', $uri))->withBody($this->http->createStream($formData->bodyToString()));
foreach ($formData->getPreparedHeaders()->all() as $header) {
/** @var \Symfony\Component\Mime\Header\HeaderInterface $header */
$request = $request->withHeader($header->getName(), $header->getBodyAsString());
}
$this->dispatch(new RequestEvent($request));
$response = $this->http->sendRequest($request);
$this->dispatch(new ResponseEvent($response, $request));
}
catch (\Throwable $e) {
throw new ClientException($e->getMessage(), $uri, $e);
}
}
/**
* Updates the properties of the specified job using the given configuration parameters.
* @param Job $job The job to update.
* @param Configuration $config The parameters to define.
*/
private function updateJob(Job $job, Configuration $config): void {
if (isset($config['repo_token'])) $job->setRepoToken($config['repo_token']);
else if (isset($config['repo_secret_token'])) $job->setRepoToken($config['repo_secret_token']);
if (isset($config['parallel'])) $job->setParallel($config['parallel'] == 'true');
if (isset($config['run_at'])) $job->setRunAt(new \DateTimeImmutable($config['run_at']));
if (isset($config['service_job_id'])) $job->setServiceJobId($config['service_job_id']);
if (isset($config['service_name'])) $job->setServiceName($config['service_name']);
if (isset($config['service_number'])) $job->setServiceNumber($config['service_number']);
if (isset($config['service_pull_request'])) $job->setServicePullRequest($config['service_pull_request']);
$hasGitData = count(array_filter($config->getKeys(), fn($key) => $key == 'service_branch' || mb_substr($key, 0, 4) == 'git_')) > 0;
if (!$hasGitData) $job->setCommitSha($config['commit_sha'] ?: '');
else {
$commit = new GitCommit($config['commit_sha'] ?: '', $config['git_message'] ?: '');
$commit->setAuthorEmail($config['git_author_email'] ?: '');
$commit->setAuthorName($config['git_author_name'] ?: '');
$commit->setCommitterEmail($config['git_committer_email'] ?: '');
$commit->setCommitterName($config['git_committer_email'] ?: '');
$job->setGit(new GitData($commit, $config['service_branch'] ?: ''));
}
}
/** @var string The URL of the default API end point. */
const defaultEndPoint = "https://coveralls.io/api/v1/";
/** @var string An event that is triggered when a request is made to the remote service. */
const eventRequest = RequestEvent::class;
/** @var string An event that is triggered when a response is received from the remote service. */
const eventResponse = ResponseEvent::class;
/** @var UriInterface The URL of the API end point. */
private UriInterface $endPoint;
/** @var Psr18Client The HTTP client. */
private Psr18Client $http;
/**
* Creates a new client.
* @param UriInterface|null $endPoint The URL of the API end point.
*/
function __construct(?UriInterface $endPoint = null) {
parent::__construct();
$this->http = new Psr18Client;
$this->endPoint = $endPoint ?? $this->http->createUri(static::defaultEndPoint);
}
/**
* Gets the URL of the API end point.
* @return UriInterface The URL of the API end point.
*/
function getEndPoint(): UriInterface {
return $this->endPoint;
}
/**
* Uploads the specified code coverage report to the Coveralls service.
* @param string $coverage A coverage report.
* @param Configuration $config The environment settings.
* @throws \InvalidArgumentException The format of the specified coverage report is not supported.
*/
function upload(string $coverage, Configuration $config = null): void {
assert(mb_strlen($coverage) > 0);
$job = null;
$report = trim($coverage);
if (mb_substr($report, 0, 5) == "<?xml" || mb_substr($report, 0, 9) == "<coverage")
$job = Clover::parseReport($report);
else {
$token = mb_substr($report, 0, 3);
if ($token == "TN:" || $token == "SF:") $job = Lcov::parseReport($report);
}
if (!$job) throw new \InvalidArgumentException("The specified coverage format is not supported.");
$this->updateJob($job, $config ?? Configuration::loadDefaults());
if (!$job->getRunAt()) $job->setRunAt(new \DateTimeImmutable);
try {
which("git");
$git = GitData::fromRepository();
$branch = ($gitData = $job->getGit()) ? $gitData->getBranch() : "";
if ($git->getBranch() == "HEAD" && mb_strlen($branch)) $git->setBranch($branch);
$job->setGit($git);
}
catch (FinderException $e) {}
$this->uploadJob($job);
}
/**
* Uploads the specified job to the Coveralls service.
* @param Job $job The job to be uploaded.
* @throws \InvalidArgumentException The job does not meet the requirements.
* @throws ClientException An error occurred while uploading the report.
*/
function uploadJob(Job $job): void {
if (!$job->getRepoToken() && !$job->getServiceName())
throw new \InvalidArgumentException("The job does not meet the requirements.");
$endPoint = $this->getEndPoint();
$uri = $endPoint->withPath("{$endPoint->getPath()}jobs");
try {
$jsonFile = json_encode($job, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$formData = new FormDataPart(["json_file" => new DataPart($jsonFile, "coveralls.json")]);
$request = ($this->http->createRequest("POST", $uri))->withBody($this->http->createStream($formData->bodyToString()));
foreach ($formData->getPreparedHeaders()->all() as $header) {
/** @var \Symfony\Component\Mime\Header\HeaderInterface $header */
$request = $request->withHeader($header->getName(), $header->getBodyAsString());
}
$this->dispatch(new RequestEvent($request));
$response = $this->http->sendRequest($request);
$this->dispatch(new ResponseEvent($response, $request));
}
catch (\Throwable $e) {
throw new ClientException($e->getMessage(), $uri, $e);
}
}
/**
* Updates the properties of the specified job using the given configuration parameters.
* @param Job $job The job to update.
* @param Configuration $config The parameters to define.
*/
private function updateJob(Job $job, Configuration $config): void {
if (isset($config["repo_token"])) $job->setRepoToken($config["repo_token"]);
else if (isset($config["repo_secret_token"])) $job->setRepoToken($config["repo_secret_token"]);
if (isset($config["parallel"])) $job->setParallel($config["parallel"] == "true");
if (isset($config["run_at"])) $job->setRunAt(new \DateTimeImmutable($config["run_at"]));
if (isset($config["service_job_id"])) $job->setServiceJobId($config["service_job_id"]);
if (isset($config["service_name"])) $job->setServiceName($config["service_name"]);
if (isset($config["service_number"])) $job->setServiceNumber($config["service_number"]);
if (isset($config["service_pull_request"])) $job->setServicePullRequest($config["service_pull_request"]);
$hasGitData = count(array_filter($config->getKeys(), fn($key) => $key == "service_branch" || mb_substr($key, 0, 4) == "git_")) > 0;
if (!$hasGitData) $job->setCommitSha($config["commit_sha"] ?: "");
else {
$commit = new GitCommit($config["commit_sha"] ?: "", $config["git_message"] ?: "");
$commit->setAuthorEmail($config["git_author_email"] ?: "");
$commit->setAuthorName($config["git_author_name"] ?: "");
$commit->setCommitterEmail($config["git_committer_email"] ?: "");
$commit->setCommitterName($config["git_committer_email"] ?: "");
$job->setGit(new GitData($commit, $config["service_branch"] ?: ""));
}
}
}

+ 19
- 19
src/ClientException.php View File

@@ -6,25 +6,25 @@ use Psr\Http\Message\{UriInterface};
/** An exception caused by an error in a `Client` request. */
class ClientException extends \RuntimeException {

/** @var UriInterface|null The URL of the HTTP request or response that failed. */
private ?UriInterface $uri;
/** @var UriInterface|null The URL of the HTTP request or response that failed. */
private ?UriInterface $uri;

/**
* Creates a new client exception.
* @param string $message A message describing the error.
* @param UriInterface|null $uri The URL of the HTTP request or response that failed.
* @param \Throwable|null $previous The previous exception used for the exception chaining.
*/
function __construct($message, ?UriInterface $uri = null, ?\Throwable $previous = null) {
parent::__construct($message, 0, $previous);
$this->uri = $uri;
}
/**
* Creates a new client exception.
* @param string $message A message describing the error.
* @param UriInterface|null $uri The URL of the HTTP request or response that failed.
* @param \Throwable|null $previous The previous exception used for the exception chaining.
*/
function __construct($message, ?UriInterface $uri = null, ?\Throwable $previous = null) {
parent::__construct($message, 0, $previous);
$this->uri = $uri;
}

/**
* Gets the URL of the HTTP request or response that failed.
* @return UriInterface|null The URL of the HTTP request or response that failed.
*/
function getUri(): ?UriInterface {
return $this->uri;
}
/**
* Gets the URL of the HTTP request or response that failed.
* @return UriInterface|null The URL of the HTTP request or response that failed.
*/
function getUri(): ?UriInterface {
return $this->uri;
}
}

+ 194
- 194
src/Configuration.php View File

@@ -12,198 +12,198 @@ use Symfony\Component\Yaml\Exception\{ParseException};
*/
class Configuration implements \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable {

/** @var array<string, string|null> The configuration parameters. */
private array $params;
/**
* Creates a new configuration.
* @param array<string, string|null> $params The configuration parameters.
*/
function __construct(array $params = []) {
$this->params = $params;
}
/**
* Creates a new configuration from the variables of the specified environment.
* @param array<string, string|null>|null $env An array providing environment variables. Defaults to `$_SERVER`.
* @return self The newly created configuration.
*/
static function fromEnvironment(array $env = null): self {
$config = new self;
$env ??= $_SERVER;
// Standard.
$serviceName = $env['CI_NAME'] ?? '';
if (mb_strlen($serviceName)) $config['service_name'] = $serviceName;
if (isset($env['CI_BRANCH'])) $config['service_branch'] = $env['CI_BRANCH'];
if (isset($env['CI_BUILD_NUMBER'])) $config['service_number'] = $env['CI_BUILD_NUMBER'];
if (isset($env['CI_BUILD_URL'])) $config['service_build_url'] = $env['CI_BUILD_URL'];
if (isset($env['CI_COMMIT'])) $config['commit_sha'] = $env['CI_COMMIT'];
if (isset($env['CI_JOB_ID'])) $config['service_job_id'] = $env['CI_JOB_ID'];
if (isset($env['CI_PULL_REQUEST']) && preg_match('/(\d+)$/', $env['CI_PULL_REQUEST'], $matches)) {
if (count($matches) >= 2) $config['service_pull_request'] = $matches[1];
}
// Coveralls.
if (isset($env['COVERALLS_REPO_TOKEN']) || isset($env['COVERALLS_TOKEN']))
$config['repo_token'] = $env['COVERALLS_REPO_TOKEN'] ?? $env['COVERALLS_TOKEN'];
if (isset($env['COVERALLS_COMMIT_SHA'])) $config['commit_sha'] = $env['COVERALLS_COMMIT_SHA'];
if (isset($env['COVERALLS_FLAG_NAME'])) $config['flag_name'] = $env['COVERALLS_FLAG_NAME'];
if (isset($env['COVERALLS_PARALLEL'])) $config['parallel'] = $env['COVERALLS_PARALLEL'];
if (isset($env['COVERALLS_RUN_AT'])) $config['run_at'] = $env['COVERALLS_RUN_AT'];
if (isset($env['COVERALLS_SERVICE_BRANCH'])) $config['service_branch'] = $env['COVERALLS_SERVICE_BRANCH'];
if (isset($env['COVERALLS_SERVICE_JOB_ID'])) $config['service_job_id'] = $env['COVERALLS_SERVICE_JOB_ID'];
if (isset($env['COVERALLS_SERVICE_NAME'])) $config['service_name'] = $env['COVERALLS_SERVICE_NAME'];
// Git.
if (isset($env['GIT_AUTHOR_EMAIL'])) $config['git_author_email'] = $env['GIT_AUTHOR_EMAIL'];
if (isset($env['GIT_AUTHOR_NAME'])) $config['git_author_name'] = $env['GIT_AUTHOR_NAME'];
if (isset($env['GIT_BRANCH'])) $config['service_branch'] = $env['GIT_BRANCH'];
if (isset($env['GIT_COMMITTER_EMAIL'])) $config['git_committer_email'] = $env['GIT_COMMITTER_EMAIL'];
if (isset($env['GIT_COMMITTER_NAME'])) $config['git_committer_name'] = $env['GIT_COMMITTER_NAME'];
if (isset($env['GIT_ID'])) $config['commit_sha'] = $env['GIT_ID'];
if (isset($env['GIT_MESSAGE'])) $config['git_message'] = $env['GIT_MESSAGE'];
// CI services.
if (isset($env['TRAVIS'])) {
$config->merge(TravisCI::getConfiguration($env));
if (mb_strlen($serviceName) && $serviceName != 'travis-ci') $config['service_name'] = $serviceName;
}
else if (isset($env['APPVEYOR'])) $config->merge(AppVeyor::getConfiguration($env));
else if (isset($env['CIRCLECI'])) $config->merge(CircleCI::getConfiguration($env));
else if ($serviceName == 'codeship') $config->merge(Codeship::getConfiguration($env));
else if (isset($env['GITHUB_WORKFLOW'])) $config->merge(GitHub::getConfiguration($env));
else if (isset($env['GITLAB_CI'])) $config->merge(GitLabCI::getConfiguration($env));
else if (isset($env['JENKINS_URL'])) $config->merge(Jenkins::getConfiguration($env));
else if (isset($env['SEMAPHORE'])) $config->merge(Semaphore::getConfiguration($env));
else if (isset($env['SURF_SHA1'])) $config->merge(Surf::getConfiguration($env));
else if (isset($env['TDDIUM'])) $config->merge(SolanoCI::getConfiguration($env));
else if (isset($env['WERCKER'])) $config->merge(Wercker::getConfiguration($env));
return $config;
}
/**
* Creates a new configuration from the specified YAML document.
* @param string $document A YAML document providing configuration parameters.
* @return self The instance corresponding to the specified YAML document.
* @throws \InvalidArgumentException The specified document is invalid.
*/
static function fromYaml(string $document): self {
assert(mb_strlen($document) > 0);
try {
$yaml = Yaml::parse($document);
if (!is_array($yaml)) throw new \InvalidArgumentException('The specified YAML document is invalid.');
return new self($yaml);
}
catch (ParseException $e) {
throw new \InvalidArgumentException('The specified YAML document is invalid.', 0, $e);
}
}
/**
* Loads the default configuration.
* The default values are read from the environment variables and an optional `.coveralls.yml` file.
* @param string $coverallsFile The path to the `.coveralls.yml` file. Defaults to the file found in the current directory.
* @return self The default configuration.
*/
static function loadDefaults(string $coverallsFile = '.coveralls.yml'): self {
assert(mb_strlen($coverallsFile) > 0);
$defaults = static::fromEnvironment();
try {
$file = new \SplFileObject($coverallsFile);
if ($file->isReadable()) $defaults->merge(static::fromYaml((string) $file->fread($file->getSize())));
return $defaults;
}
catch (\Throwable $e) {
return $defaults;
}
}
/**
* Gets the number of entries in this configuration.
* @return int The number of entries in this configuration.
*/
function count(): int {
return count($this->params);
}
/**
* Returns a new iterator that allows iterating the elements of this configuration.
* @return \Traversable<string, string|null> An iterator for the elements of this configuration.
*/
function getIterator(): \Traversable {
return new \ArrayIterator($this->params);
}
/**
* Gets the keys of this configuration.
* @return string[] The keys of this configuration.
*/
function getKeys(): array {
return array_keys($this->params);
}
/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
return (object) $this->params;
}
/**
* Adds all entries of the specified configuration to this one, ignoring `null` values.
* @param self $config The configuration to be merged.
*/
function merge(self $config): void {
foreach ($config as $key => $value)
if ($value !== null) $this[$key] = $value;
}
/**
* Gets a value indicating whether this configuration contains the specified key.
* @param string $key The key to seek for.
* @return bool `true` if this configuration contains the specified key, otherwiser `false`.
*/
function offsetExists($key): bool {
assert(is_string($key) && mb_strlen($key) > 0);
return isset($this->params[$key]);
}
/**
* Gets the value associated to the specified key.
* @param string $key The key to seek for.
* @return string The value, or a `null` reference is the key is not found.
*/
function offsetGet($key): ?string {
assert(is_string($key) && mb_strlen($key) > 0);
return $this->params[$key] ?? null;
}
/**
* Associates a given value to the specified key.
* @param string $key The key to seek for.
* @param string $value The new value.
*/
function offsetSet($key, $value): void {
assert(is_string($key) && mb_strlen($key) > 0);
$this->params[$key] = $value;
}
/**
* Removes the value associated to the specified key.
* @param string $key The key to seek for.
*/
function offsetUnset($key): void {
assert(is_string($key) && mb_strlen($key) > 0);
unset($this->params[$key]);
}
/** @var array<string, string|null> The configuration parameters. */
private array $params;
/**
* Creates a new configuration.
* @param array<string, string|null> $params The configuration parameters.
*/
function __construct(array $params = []) {
$this->params = $params;
}
/**
* Creates a new configuration from the variables of the specified environment.
* @param array<string, string|null>|null $env An array providing environment variables. Defaults to `$_SERVER`.
* @return self The newly created configuration.
*/
static function fromEnvironment(array $env = null): self {
$config = new self;
$env ??= $_SERVER;
// Standard.
$serviceName = $env["CI_NAME"] ?? "";
if (mb_strlen($serviceName)) $config["service_name"] = $serviceName;
if (isset($env["CI_BRANCH"])) $config["service_branch"] = $env["CI_BRANCH"];
if (isset($env["CI_BUILD_NUMBER"])) $config["service_number"] = $env["CI_BUILD_NUMBER"];
if (isset($env["CI_BUILD_URL"])) $config["service_build_url"] = $env["CI_BUILD_URL"];
if (isset($env["CI_COMMIT"])) $config["commit_sha"] = $env["CI_COMMIT"];
if (isset($env["CI_JOB_ID"])) $config["service_job_id"] = $env["CI_JOB_ID"];
if (isset($env["CI_PULL_REQUEST"]) && preg_match('/(\d+)$/', $env["CI_PULL_REQUEST"], $matches)) {
if (count($matches) >= 2) $config["service_pull_request"] = $matches[1];
}
// Coveralls.
if (isset($env["COVERALLS_REPO_TOKEN"]) || isset($env["COVERALLS_TOKEN"]))
$config["repo_token"] = $env["COVERALLS_REPO_TOKEN"] ?? $env["COVERALLS_TOKEN"];
if (isset($env["COVERALLS_COMMIT_SHA"])) $config["commit_sha"] = $env["COVERALLS_COMMIT_SHA"];
if (isset($env["COVERALLS_FLAG_NAME"])) $config["flag_name"] = $env["COVERALLS_FLAG_NAME"];
if (isset($env["COVERALLS_PARALLEL"])) $config["parallel"] = $env["COVERALLS_PARALLEL"];
if (isset($env["COVERALLS_RUN_AT"])) $config["run_at"] = $env["COVERALLS_RUN_AT"];
if (isset($env["COVERALLS_SERVICE_BRANCH"])) $config["service_branch"] = $env["COVERALLS_SERVICE_BRANCH"];
if (isset($env["COVERALLS_SERVICE_JOB_ID"])) $config["service_job_id"] = $env["COVERALLS_SERVICE_JOB_ID"];
if (isset($env["COVERALLS_SERVICE_NAME"])) $config["service_name"] = $env["COVERALLS_SERVICE_NAME"];
// Git.
if (isset($env["GIT_AUTHOR_EMAIL"])) $config["git_author_email"] = $env["GIT_AUTHOR_EMAIL"];
if (isset($env["GIT_AUTHOR_NAME"])) $config["git_author_name"] = $env["GIT_AUTHOR_NAME"];
if (isset($env["GIT_BRANCH"])) $config["service_branch"] = $env["GIT_BRANCH"];
if (isset($env["GIT_COMMITTER_EMAIL"])) $config["git_committer_email"] = $env["GIT_COMMITTER_EMAIL"];
if (isset($env["GIT_COMMITTER_NAME"])) $config["git_committer_name"] = $env["GIT_COMMITTER_NAME"];
if (isset($env["GIT_ID"])) $config["commit_sha"] = $env["GIT_ID"];
if (isset($env["GIT_MESSAGE"])) $config["git_message"] = $env["GIT_MESSAGE"];
// CI services.
if (isset($env["TRAVIS"])) {
$config->merge(TravisCI::getConfiguration($env));
if (mb_strlen($serviceName) && $serviceName != "travis-ci") $config["service_name"] = $serviceName;
}
else if (isset($env["APPVEYOR"])) $config->merge(AppVeyor::getConfiguration($env));
else if (isset($env["CIRCLECI"])) $config->merge(CircleCI::getConfiguration($env));
else if ($serviceName == "codeship") $config->merge(Codeship::getConfiguration($env));
else if (isset($env["GITHUB_WORKFLOW"])) $config->merge(GitHub::getConfiguration($env));
else if (isset($env["GITLAB_CI"])) $config->merge(GitLabCI::getConfiguration($env));
else if (isset($env["JENKINS_URL"])) $config->merge(Jenkins::getConfiguration($env));
else if (isset($env["SEMAPHORE"])) $config->merge(Semaphore::getConfiguration($env));
else if (isset($env["SURF_SHA1"])) $config->merge(Surf::getConfiguration($env));
else if (isset($env["TDDIUM"])) $config->merge(SolanoCI::getConfiguration($env));
else if (isset($env["WERCKER"])) $config->merge(Wercker::getConfiguration($env));
return $config;
}
/**
* Creates a new configuration from the specified YAML document.
* @param string $document A YAML document providing configuration parameters.
* @return self The instance corresponding to the specified YAML document.
* @throws \InvalidArgumentException The specified document is invalid.
*/
static function fromYaml(string $document): self {
assert(mb_strlen($document) > 0);
try {
$yaml = Yaml::parse($document);
if (!is_array($yaml)) throw new \InvalidArgumentException("The specified YAML document is invalid.");
return new self($yaml);
}
catch (ParseException $e) {
throw new \InvalidArgumentException("The specified YAML document is invalid.", 0, $e);
}
}
/**
* Loads the default configuration.
* The default values are read from the environment variables and an optional `.coveralls.yml` file.
* @param string $coverallsFile The path to the `.coveralls.yml` file. Defaults to the file found in the current directory.
* @return self The default configuration.
*/
static function loadDefaults(string $coverallsFile = ".coveralls.yml"): self {
assert(mb_strlen($coverallsFile) > 0);
$defaults = static::fromEnvironment();
try {
$file = new \SplFileObject($coverallsFile);
if ($file->isReadable()) $defaults->merge(static::fromYaml((string) $file->fread($file->getSize())));
return $defaults;
}
catch (\Throwable $e) {
return $defaults;
}
}
/**
* Gets the number of entries in this configuration.
* @return int The number of entries in this configuration.
*/
function count(): int {
return count($this->params);
}
/**
* Returns a new iterator that allows iterating the elements of this configuration.
* @return \Traversable<string, string|null> An iterator for the elements of this configuration.
*/
function getIterator(): \Traversable {
return new \ArrayIterator($this->params);
}
/**
* Gets the keys of this configuration.
* @return string[] The keys of this configuration.
*/
function getKeys(): array {
return array_keys($this->params);
}
/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
return (object) $this->params;
}
/**
* Adds all entries of the specified configuration to this one, ignoring `null` values.
* @param self $config The configuration to be merged.
*/
function merge(self $config): void {
foreach ($config as $key => $value)
if ($value !== null) $this[$key] = $value;
}
/**
* Gets a value indicating whether this configuration contains the specified key.
* @param string $key The key to seek for.
* @return bool `true` if this configuration contains the specified key, otherwiser `false`.
*/
function offsetExists($key): bool {
assert(is_string($key) && mb_strlen($key) > 0);
return isset($this->params[$key]);
}
/**
* Gets the value associated to the specified key.
* @param string $key The key to seek for.
* @return string The value, or a `null` reference is the key is not found.
*/
function offsetGet($key): ?string {
assert(is_string($key) && mb_strlen($key) > 0);
return $this->params[$key] ?? null;
}
/**
* Associates a given value to the specified key.
* @param string $key The key to seek for.
* @param string $value The new value.
*/
function offsetSet($key, $value): void {
assert(is_string($key) && mb_strlen($key) > 0);
$this->params[$key] = $value;
}
/**
* Removes the value associated to the specified key.
* @param string $key The key to seek for.
*/
function offsetUnset($key): void {
assert(is_string($key) && mb_strlen($key) > 0);
unset($this->params[$key]);
}
}

+ 143
- 143
src/GitCommit.php View File

@@ -4,147 +4,147 @@ namespace Coveralls;
/** Represents a Git commit. */
class GitCommit implements \JsonSerializable {

/** @var string The author mail address. */
private string $authorEmail = '';
/** @var string The author name. */
private string $authorName = '';
/** @var string The committer mail address. */
private string $committerEmail = '';
/** @var string The committer name. */
private string $committerName = '';
/** @var string The commit identifier. */
private string $id;
/** @var string The commit message. */
private string $message;
/**
* Creates a new Git commit.
* @param string $id The commit identifier.
* @param string $message The commit message.
*/
function __construct(string $id, string $message = '') {
$this->id = $id;
$this->message = $message;
}
/**
* Creates a new Git commit from the specified JSON object.
* @param object $map A JSON object representing a Git commit.
* @return self The instance corresponding to the specified JSON object.
*/
static function fromJson(object $map): self {
return (new self(isset($map->id) && is_string($map->id) ? $map->id : '', isset($map->message) && is_string($map->message) ? $map->message : ''))
->setAuthorEmail(isset($map->author_email) && is_string($map->author_email) ? $map->author_email : '')
->setAuthorName(isset($map->author_name) && is_string($map->author_name) ? $map->author_name : '')
->setCommitterEmail(isset($map->committer_email) && is_string($map->committer_email) ? $map->committer_email : '')
->setCommitterName(isset($map->committer_name) && is_string($map->committer_name) ? $map->committer_name : '');
}
/**
* Gets the author mail address.
* @return string The author mail address.
*/
function getAuthorEmail(): string {
return $this->authorEmail;
}
/**
* Gets the author name.
* @return string The author name.
*/
function getAuthorName(): string {
return $this->authorName;
}
/**
* Gets the committer mail address.
* @return string The committer mail address.
*/
function getCommitterEmail(): string {
return $this->committerEmail;
}
/**
* Gets the committer name.
* @return string The committer name.
*/
function getCommitterName(): string {
return $this->committerName;
}
/**
* Gets the commit identifier.
* @return string The commit identifier.
*/
function getId(): string {
return $this->id;
}
/**
* Gets the commit message.
* @return string The commit message.
*/
function getMessage(): string {
return $this->message;
}
/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
$map = new \stdClass;
$map->id = $this->getId();
if (mb_strlen($authorEmail = $this->getAuthorEmail())) $map->author_email = $authorEmail;
if (mb_strlen($authorName = $this->getAuthorName())) $map->author_name = $authorName;
if (mb_strlen($committerEmail = $this->getCommitterEmail())) $map->committer_email = $committerEmail;
if (mb_strlen($committerName = $this->getCommitterName())) $map->committer_name = $committerName;
if (mb_strlen($message = $this->getMessage())) $map->message = $message;
return $map;
}
/**
* Sets the author mail address.
* @param string $value The new mail address.
* @return $this This instance.
*/
function setAuthorEmail(string $value): self {
$this->authorEmail = $value;
return $this;
}
/**
* Sets the author name.
* @param string $value The new name.
* @return $this This instance.
*/
function setAuthorName(string $value): self {
$this->authorName = $value;
return $this;
}
/**
* Sets the committer mail address.
* @param string $value The new mail address.
* @return $this This instance.
*/
function setCommitterEmail(string $value): self {
$this->committerEmail = $value;
return $this;
}
/**
* Sets the committer name.
* @param string $value The new name.
* @return $this This instance.
*/
function setCommitterName(string $value): self {
$this->committerName = $value;
return $this;
}
/** @var string The author mail address. */
private string $authorEmail = "";
/** @var string The author name. */
private string $authorName = "";
/** @var string The committer mail address. */
private string $committerEmail = "";
/** @var string The committer name. */
private string $committerName = "";
/** @var string The commit identifier. */
private string $id;
/** @var string The commit message. */
private string $message;
/**
* Creates a new Git commit.
* @param string $id The commit identifier.
* @param string $message The commit message.
*/
function __construct(string $id, string $message = "") {
$this->id = $id;
$this->message = $message;
}
/**
* Creates a new Git commit from the specified JSON object.
* @param object $map A JSON object representing a Git commit.
* @return self The instance corresponding to the specified JSON object.
*/
static function fromJson(object $map): self {
return (new self(isset($map->id) && is_string($map->id) ? $map->id : "", isset($map->message) && is_string($map->message) ? $map->message : ""))
->setAuthorEmail(isset($map->author_email) && is_string($map->author_email) ? $map->author_email : "")
->setAuthorName(isset($map->author_name) && is_string($map->author_name) ? $map->author_name : "")
->setCommitterEmail(isset($map->committer_email) && is_string($map->committer_email) ? $map->committer_email : "")
->setCommitterName(isset($map->committer_name) && is_string($map->committer_name) ? $map->committer_name : "");
}
/**
* Gets the author mail address.
* @return string The author mail address.
*/
function getAuthorEmail(): string {
return $this->authorEmail;
}
/**
* Gets the author name.
* @return string The author name.
*/
function getAuthorName(): string {
return $this->authorName;
}
/**
* Gets the committer mail address.
* @return string The committer mail address.
*/
function getCommitterEmail(): string {
return $this->committerEmail;
}
/**
* Gets the committer name.
* @return string The committer name.
*/
function getCommitterName(): string {
return $this->committerName;
}
/**
* Gets the commit identifier.
* @return string The commit identifier.
*/
function getId(): string {
return $this->id;
}
/**
* Gets the commit message.
* @return string The commit message.
*/
function getMessage(): string {
return $this->message;
}
/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
$map = new \stdClass;
$map->id = $this->getId();
if (mb_strlen($authorEmail = $this->getAuthorEmail())) $map->author_email = $authorEmail;
if (mb_strlen($authorName = $this->getAuthorName())) $map->author_name = $authorName;
if (mb_strlen($committerEmail = $this->getCommitterEmail())) $map->committer_email = $committerEmail;
if (mb_strlen($committerName = $this->getCommitterName())) $map->committer_name = $committerName;
if (mb_strlen($message = $this->getMessage())) $map->message = $message;
return $map;
}
/**
* Sets the author mail address.
* @param string $value The new mail address.
* @return $this This instance.
*/
function setAuthorEmail(string $value): self {
$this->authorEmail = $value;
return $this;
}
/**
* Sets the author name.
* @param string $value The new name.
* @return $this This instance.
*/
function setAuthorName(string $value): self {
$this->authorName = $value;
return $this;
}
/**
* Sets the committer mail address.
* @param string $value The new mail address.
* @return $this This instance.
*/
function setCommitterEmail(string $value): self {
$this->committerEmail = $value;
return $this;
}
/**
* Sets the committer name.
* @param string $value The new name.
* @return $this This instance.
*/
function setCommitterName(string $value): self {
$this->committerName = $value;
return $this;
}
}

+ 98
- 98
src/GitData.php View File

@@ -4,115 +4,115 @@ namespace Coveralls;
/** Represents Git data that can be used to display more information to users. */
class GitData implements \JsonSerializable {

/** @var string The branch name. */
private string $branch;
/** @var string The branch name. */
private string $branch;

/** @var GitCommit|null The Git commit. */
private ?GitCommit $commit;
/** @var GitCommit|null The Git commit. */
private ?GitCommit $commit;

/** @var \ArrayObject<int, GitRemote> The remote repositories. */
private \ArrayObject $remotes;
/** @var \ArrayObject<int, GitRemote> The remote repositories. */
private \ArrayObject $remotes;

/**
* Creates a new Git data.
* @param GitCommit $commit The Git commit.
* @param string $branch The branch name.
* @param GitRemote[] $remotes The remote repositories.
*/
function __construct(?GitCommit $commit, string $branch = '', array $remotes = []) {
$this->setBranch($branch);
$this->commit = $commit;
$this->remotes = new \ArrayObject($remotes);
}
/**
* Creates a new Git data.
* @param GitCommit $commit The Git commit.
* @param string $branch The branch name.
* @param GitRemote[] $remotes The remote repositories.
*/
function __construct(?GitCommit $commit, string $branch = "", array $remotes = []) {
$this->setBranch($branch);
$this->commit = $commit;
$this->remotes = new \ArrayObject($remotes);
}

/**
* Creates a new Git data from the specified JSON object.
* @param object $map A JSON object representing a Git data.
* @return self The instance corresponding to the specified JSON object.
*/
static function fromJson(object $map): self {
return new self(
isset($map->head) && is_object($map->head) ? GitCommit::fromJson($map->head) : null,
isset($map->branch) && is_string($map->branch) ? $map->branch : '',
isset($map->remotes) && is_array($map->remotes) ? array_map([GitRemote::class, 'fromJson'], $map->remotes) : []
);
}
/**
* Creates a new Git data from the specified JSON object.
* @param object $map A JSON object representing a Git data.
* @return self The instance corresponding to the specified JSON object.
*/
static function fromJson(object $map): self {
return new self(
isset($map->head) && is_object($map->head) ? GitCommit::fromJson($map->head) : null,
isset($map->branch) && is_string($map->branch) ? $map->branch : "",
isset($map->remotes) && is_array($map->remotes) ? array_map([GitRemote::class, "fromJson"], $map->remotes) : []
);
}

/**
* Creates a new Git data from a local repository.
* This method relies on the availability of the Git executable in the system path.
* @param string $path The path to the repository folder. Defaults to the current working directory.
* @return self The newly created Git data.
*/
static function fromRepository(string $path = ''): self {
$workingDir = getcwd() ?: '.';
if (!mb_strlen($path)) $path = $workingDir;
chdir($path);
/**
* Creates a new Git data from a local repository.
* This method relies on the availability of the Git executable in the system path.
* @param string $path The path to the repository folder. Defaults to the current working directory.
* @return self The newly created Git data.
*/
static function fromRepository(string $path = ""): self {
$workingDir = getcwd() ?: ".";
if (!mb_strlen($path)) $path = $workingDir;
chdir($path);

$commands = (object) array_map(fn($command) => trim(`git $command`), [
'author_email' => 'log -1 --pretty=format:%ae',
'author_name' => 'log -1 --pretty=format:%aN',
'branch' => 'rev-parse --abbrev-ref HEAD',
'committer_email' => 'log -1 --pretty=format:%ce',
'committer_name' => 'log -1 --pretty=format:%cN',
'id' => 'log -1 --pretty=format:%H',
'message' => 'log -1 --pretty=format:%s',
'remotes' => 'remote -v'
]);
$commands = (object) array_map(fn($command) => trim(`git $command`), [
"author_email" => "log -1 --pretty=format:%ae",
"author_name" => "log -1 --pretty=format:%aN",
"branch" => "rev-parse --abbrev-ref HEAD",
"committer_email" => "log -1 --pretty=format:%ce",
"committer_name" => "log -1 --pretty=format:%cN",
"id" => "log -1 --pretty=format:%H",
"message" => "log -1 --pretty=format:%s",
"remotes" => "remote -v"
]);

$remotes = [];
foreach (preg_split('/\r?\n/', $commands->remotes) ?: [] as $remote) {
$parts = explode(' ', (string) preg_replace('/\s+/', ' ', $remote));
$remotes[$parts[0]] ??= new GitRemote($parts[0], count($parts) > 1 ? $parts[1] : null);
}
$remotes = [];
foreach (preg_split('/\r?\n/', $commands->remotes) ?: [] as $remote) {
$parts = explode(" ", (string) preg_replace('/\s+/', " ", $remote));
$remotes[$parts[0]] ??= new GitRemote($parts[0], count($parts) > 1 ? $parts[1] : null);
}

chdir($workingDir);
return new self(GitCommit::fromJson($commands), $commands->branch, array_values($remotes));
}
chdir($workingDir);
return new self(GitCommit::fromJson($commands), $commands->branch, array_values($remotes));
}

/**
* Gets the branch name.
* @return string The branch name.
*/
function getBranch(): string {
return $this->branch;
}
/**
* Gets the branch name.
* @return string The branch name.
*/
function getBranch(): string {
return $this->branch;
}

/**
* Gets the Git commit.
* @return GitCommit The Git commit.
*/
function getCommit(): ?GitCommit {
return $this->commit;
}
/**
* Gets the Git commit.
* @return GitCommit The Git commit.
*/
function getCommit(): ?GitCommit {
return $this->commit;
}

/**
* Gets the remote repositories.
* @return \ArrayObject<int, GitRemote> The remote repositories.
*/
function getRemotes(): \ArrayObject {
return $this->remotes;
}
/**
* Gets the remote repositories.
* @return \ArrayObject<int, GitRemote> The remote repositories.
*/
function getRemotes(): \ArrayObject {
return $this->remotes;
}

/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
return (object) [
'branch' => $this->getBranch(),
'head' => ($commit = $this->getCommit()) ? $commit->jsonSerialize() : null,
'remotes' => array_map(fn(GitRemote $item) => $item->jsonSerialize(), (array) $this->getRemotes())
];
}
/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
return (object) [
"branch" => $this->getBranch(),
"head" => ($commit = $this->getCommit()) ? $commit->jsonSerialize() : null,
"remotes" => array_map(fn(GitRemote $item) => $item->jsonSerialize(), (array) $this->getRemotes())
];
}

/**
* Sets the branch name.
* @param string $value The new name.
* @return $this This instance.
*/
function setBranch(string $value): self {
$this->branch = $value;
return $this;
}
/**
* Sets the branch name.
* @param string $value The new name.
* @return $this This instance.
*/
function setBranch(string $value): self {
$this->branch = $value;
return $this;
}
}

+ 49
- 49
src/GitRemote.php View File

@@ -7,59 +7,59 @@ use Psr\Http\Message\{UriInterface};
/** Represents a Git remote repository. */
class GitRemote implements \JsonSerializable {

/** @var string The remote's name. */
private string $name;
/** @var string The remote's name. */
private string $name;

/** @var UriInterface|null The remote's URL. */
private ?UriInterface $url;
/** @var UriInterface|null The remote's URL. */
private ?UriInterface $url;

/**
* Creates a new Git remote repository.
* @param string $name The remote's name.
* @param UriInterface|string|null $url The remote's URL.
*/
function __construct(string $name, $url = null) {
$this->name = $name;
$this->url = !is_string($url) ? $url :
new Uri(preg_match('#^\w+://#', $url) ? $url : (string) preg_replace('/^([^@]+@)?([^:]+):(.+)$/', 'ssh://$1$2/$3', $url));
}
/**
* Creates a new Git remote repository.
* @param string $name The remote's name.
* @param UriInterface|string|null $url The remote's URL.
*/
function __construct(string $name, $url = null) {
$this->name = $name;
$this->url = !is_string($url) ? $url :
new Uri(preg_match('#^\w+://#', $url) ? $url : (string) preg_replace('/^([^@]+@)?([^:]+):(.+)$/', 'ssh://$1$2/$3', $url));
}

/**
* Creates a new remote repository from the specified JSON object.
* @param object $map A JSON object representing a remote repository.
* @return self The instance corresponding to the specified JSON object.
*/
static function fromJson(object $map): self {
return new self(
isset($map->name) && is_string($map->name) ? $map->name : '',
isset($map->url) && is_string($map->url) ? $map->url : null
);
}
/**
* Creates a new remote repository from the specified JSON object.
* @param object $map A JSON object representing a remote repository.
* @return self The instance corresponding to the specified JSON object.
*/
static function fromJson(object $map): self {
return new self(
isset($map->name) && is_string($map->name) ? $map->name : "",
isset($map->url) && is_string($map->url) ? $map->url : null
);
}

/**
* Gets the name of this remote.
* @return string The remote's name.
*/
function getName(): string {
return $this->name;
}
/**
* Gets the name of this remote.
* @return string The remote's name.
*/
function getName(): string {
return $this->name;
}

/**
* Gets the URL of this remote.
* @return UriInterface|null The remote's URL.
*/
function getUrl(): ?UriInterface {
return $this->url;
}
/**
* Gets the URL of this remote.
* @return UriInterface|null The remote's URL.
*/
function getUrl(): ?UriInterface {
return $this->url;
}

/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
return (object) [
'name' => $this->getName(),
'url' => ($url = $this->getUrl()) ? (string) $url : null
];
}
/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
return (object) [
"name" => $this->getName(),
"url" => ($url = $this->getUrl()) ? (string) $url : null
];
}
}

+ 269
- 269
src/Job.php View File

@@ -4,273 +4,273 @@ namespace Coveralls;
/** Represents the coverage data from a single run of a test suite. */
class Job implements \JsonSerializable {

/** @var string The current SHA of the commit being built to override the `git` parameter. */
private string $commitSha = '';
/** @var string The job name. */
private string $flagName = '';
/** @var GitData|null The Git data that can be used to display more information to users. */
private ?GitData $git = null;
/** @var bool Value indicating whether the build will not be considered done until a webhook has been sent to Coveralls. */
private bool $isParallel = false;
/** @var string The secret token for the repository. */
private string $repoToken = '';
/** @var \DateTimeInterface|null The timestamp of when the job ran. */
private ?\DateTimeInterface $runAt = null;
/** @var string The unique identifier of the job on the CI service. */
private string $serviceJobId = '';
/** @var string The CI service or other environment in which the test suite was run. */
private string $serviceName = '';
/** @var string The build number. */
private string $serviceNumber = '';
/** @var string The associated pull request identifier of the build. */
private string $servicePullRequest = '';
/** @var \ArrayObject<int, SourceFile> The list of source files. */
private \ArrayObject $sourceFiles;