implement JS password scoring, td_stock approve logicc

This commit is contained in:
Thanakorn S
2026-04-30 15:14:42 +07:00
parent db5c47b6ca
commit ac4fd9a5ad
3147 changed files with 1298 additions and 343 deletions
@@ -0,0 +1,40 @@
build: false
version: dev-{build}
shallow_clone: false
clone_folder: C:\projects\php-coveralls
environment:
matrix:
- php_ver: 7.1.2
cache:
- '%APPDATA%\Composer'
- '%LOCALAPPDATA%\Composer'
- C:\tools\php -> .appveyor.yml
- C:\tools\composer.phar -> .appveyor.yml
init:
- SET PATH=C:\tools\php;%PATH%
install:
- ps: Set-Service wuauserv -StartupType Manual
- IF NOT EXIST C:\tools\php (choco install --yes --allow-empty-checksums php --version %php_ver% --params '/InstallDir:C:\tools\php')
- appveyor DownloadFile http://curl.haxx.se/ca/cacert.pem -FileName C:\cacert.pem
- cd C:\tools\php
- copy php.ini-production php.ini
- echo date.timezone=UTC >> php.ini
- echo memory_limit=512M >> php.ini
- echo extension_dir=ext >> php.ini
- echo extension=php_curl.dll >> php.ini
- echo extension=php_openssl.dll >> php.ini
- echo curl.cainfo=C:\cacert.pem >> php.ini
- IF NOT EXIST C:\tools\composer.phar (cd C:\tools && appveyor DownloadFile https://getcomposer.org/download/1.4.1/composer.phar)
- php C:\tools\composer.phar global show -ND 2>1 | findstr "hirak/prestissimo" || php C:\tools\composer.phar global require hirak/prestissimo
before_test:
- cd C:\projects\php-coveralls
- php C:\tools\composer.phar update --no-interaction --no-progress --prefer-stable --no-ansi
test_script:
- cd C:\projects\php-coveralls
- phpdbg -qrr vendor\phpunit\phpunit\phpunit --verbose
@@ -0,0 +1,204 @@
name: CI
on:
- pull_request
- push
jobs:
tests:
name: "${{matrix.os}} | PHP${{matrix.php}} Symfony@${{matrix.symfony || '*'}} ${{ matrix.composer-flags}}"
runs-on: ${{matrix.os}}
strategy:
fail-fast: false
matrix:
include:
- php: '5.5'
os: ubuntu-latest
composer-flags: '--prefer-lowest'
coverage: 'xdebug'
- php: '5.5'
os: ubuntu-latest
symfony: '^3.0'
- php: '5.5'
os: windows-latest
coverage: 'xdebug'
- php: '5.6'
os: ubuntu-latest
- php: '7.0'
os: ubuntu-latest
- php: '7.1'
os: ubuntu-latest
symfony: '^4.0'
- php: '7.2'
os: ubuntu-latest
symfony: '^5.0'
- php: '7.3'
os: ubuntu-latest
- php: '7.4'
os: ubuntu-latest
- php: '8.0'
os: ubuntu-latest
symfony: '^6.0'
- php: '8.1'
os: ubuntu-latest
symfony: '^6.0'
- php: '8.2'
os: ubuntu-latest
symfony: '^6.0'
coverage: 'pcov'
- php: '8.2'
os: ubuntu-latest
symfony: '^6.0'
coverage: 'xdebug'
- php: '8.2'
os: windows-latest
symfony: '^6.0'
coverage: 'pcov'
- php: '8.3'
os: ubuntu-latest
symfony: '^6.0'
continue-on-error: ${{ matrix.php == '8.3' }}
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Get symfony flex version
uses: actions/github-script@v3.1
id: flex-version
with:
script: 'return "${{ matrix.php }}" == "7.0" ? "~1.6.0" : "^1.11"'
result-encoding: string
- name: PHP setup
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
ini-values: zend.assertions=1, error_reporting=-1, display_errors=On, log_errors_max_len=0
coverage: ${{ matrix.coverage || 'none' }}
- name: Downgrade composer for old Symfony Flex
if: steps.flex-version.outputs.result == '~1.6.0'
run: composer self-update --1
- name: "Disallow Symfony Flex to run as a Composer plugin (global)"
if: startsWith(matrix.php, '5') == false && steps.flex-version.outputs.result != '~1.6.0'
run: composer -g config allow-plugins.symfony/flex false
- name: "Install Symfony Flex"
if: startsWith(matrix.php, '5') == false
run: composer global require -o --no-interaction symfony/flex:${{ steps.flex-version.outputs.result }} --no-plugins
- name: Require Symfony version
if: matrix.symfony
run: composer config extra.symfony.require ${{ matrix.symfony }}
- name: Update composer dependencies
if: matrix.php != '8.3'
run: composer update -o --no-interaction --no-progress ${{ matrix.composer-flags }}
- name: "Update composer dependencies (PHP 8.2, ignore platform)"
if: matrix.php == '8.3'
run: composer update -o --no-interaction --no-progress ${{ matrix.composer-flags }} --ignore-platform-req=php+
- name: Run test suite
run: php vendor/bin/phpunit -v
- name: Upload coverage results to Coveralls
if: matrix.coverage
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_PARALLEL: true
COVERALLS_FLAG_NAME: "${{matrix.os}} | PHP${{matrix.php}} Symfony@${{matrix.symfony || '*'}}"
run: php bin/php-coveralls -v --exclude-no-stmt
coveralls-finish:
needs: tests
runs-on: ubuntu-latest
steps:
- name: Notify Coveralls when build is finished
run: |
curl -k https://coveralls.io/webhook?repo_token=${{ secrets.GITHUB_TOKEN }} \
-d "repo_name=$GITHUB_REPOSITORY&payload[build_num]=$GITHUB_RUN_ID&payload[status]=done"
build-phar:
needs: tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
extensions: phar, openssl, sodium
coverage: none
ini-values: phar.readonly=Off, error_reporting=-1, display_errors=On, zend.assertions=1
# Autoload files generated with Composer 2.3+ are not compatible with PHP < 7.0.
tools: composer:2.2
- name: Set Composer platform
run: composer config platform.php 5.5.0
- name: Install Composer dependencies
uses: ramsey/composer-install@v2
with:
composer-options: "--no-dev --optimize-autoloader"
# Note: do NOT turn on the requirement checker in the box config as it is no longer
# compatible with PHP < 7.2.
- name: Install Box
run: wget https://github.com/humbug/box/releases/latest/download/box.phar -O box.phar && chmod 0755 box.phar
- name: Validate configuration
run: php box.phar validate -i box.json
- name: Building binary...
run: php box.phar compile -v --config=box.json
- name: Show info about the build phar
run: php box.phar info -l ./build/artifacts/php-coveralls.phar
- name: Upload the Phar artifact
uses: actions/upload-artifact@v3
with:
name: php-coveralls-phar
path: ./build/artifacts/php-coveralls.phar
retention-days: 30
test-phar:
needs: build-phar
name: "Test Phar PHP ${{ matrix.php }}"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- php: '5.5'
- php: '7.2'
- php: '8.0'
- php: '8.3'
continue-on-error: ${{ matrix.php == '8.3' }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Download the Phar artifact
uses: actions/download-artifact@v3
with:
name: php-coveralls-phar
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
ini-values: error_reporting=-1, display_errors=On, zend.assertions=1
coverage: none
- name: Test the Phar is functional
run: php php-coveralls.phar --version
@@ -0,0 +1,26 @@
name: Static Code Analysis
on:
- pull_request
- push
jobs:
static-code-analysis:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
coverage: none
- name: Validate composer.json
run: composer validate
- name: Run all checks
run: |
composer install-dev-tools
composer sca
@@ -0,0 +1,30 @@
# phpstorm
.idea
# eclipse
.buildpath
.project
.settings/
# PHP prj
build/
cache.properties
# composer
vendor/
composer.lock
# travis
travis/
# bin
composer.phar
php-cs-fixer.phar
.coveralls.yml
.php-cs-fixer.php
.php-cs-fixer.cache
.phpunit.result.cache
phpunit.xml
box.phar
@@ -0,0 +1,33 @@
<?php
$finder = PhpCsFixer\Finder::create()
->ignoreDotFiles(false)
->ignoreVCSIgnored(true)
->exclude('tests/Fixture')
->in(__DIR__)
;
$config = new PhpCsFixer\Config();
$config
->setRiskyAllowed(true)
->setRules([
'@PhpCsFixer' => true,
'@PhpCsFixer:risky' => true,
'yoda_style' => [
'equal' => false,
'identical' => false,
'less_and_greater' => false,
],
'concat_space' => ['spacing' => 'one'],
'explicit_indirect_variable' => false,
'explicit_string_variable' => false,
'method_chaining_indentation' => false,
'php_unit_test_annotation' => false,
'single_line_comment_style' => false,
'ternary_to_elvis_operator' => false,
'visibility_required' => ['elements' => ['method', 'property']],
])
->setFinder($finder)
;
return $config;
@@ -0,0 +1,308 @@
CHANGELOG
=========
## 2.6.0
### Miscellaneous
* Add windows-os to github action (#335)
* DX: Update PHP CS Fixer to ^3.13.2 (#355)
* DX: update dev-tools, especially PHP CS Fixer (#366)
* Fix PHP 8.1 compatibility (#361)
* GH Actions/CI: run the test suite against PHP 8.1, 8.2 and 8.3 (#363)
* README: fix broken badge and mention GH Actions (#362)
* Replace Travis CI with the GitHub action badge (#364)
* Upgrade PHAR generation to Box 4.x (#351)
## 2.5.3
### Miscellaneous
- GH Actions: various updates (#346)
- MetricsTest: fix tests failing on imprecise floats (#344)
- PHP 8.1 | Fix "passing null to non-nullable" deprecations (tests only) (#345)
- PHP 8.2 | Fix deprecated embedded variables in text strings (#343)
- Fix minor typo in GitHub Action step's name (#349)
## 2.5.2
### Bug fix
- [#330](https://github.com/php-coveralls/php-coveralls/pull/330) Changed `CIRCLE_BUILD_NUM` to `CIRCLE_WORKFLOW_ID`
- [#328](https://github.com/php-coveralls/php-coveralls/pull/328)
Added compatibility with Symfony 6
### Miscellaneous
- [#325](https://github.com/php-coveralls/php-coveralls/pull/325) CI: Migrate to GitHub Actions
## 2.5.1
### Bug fix
- [#324](https://github.com/php-coveralls/php-coveralls/pull/324) Fix PHP 5.5 compatibility
## 2.5.0
### Miscellaneous
- [#322](https://github.com/php-coveralls/php-coveralls/pull/322) Allow for Symfony:^6
- [#321](https://github.com/php-coveralls/php-coveralls/pull/321) Disallow `psr/log` v3
- [#319](https://github.com/php-coveralls/php-coveralls/pull/319) Added fallback to show where is problem with non-UTF8 char
- [#317](https://github.com/php-coveralls/php-coveralls/pull/317) Allow `psr/log` v2 and v3
- [#316](https://github.com/php-coveralls/php-coveralls/pull/316) Update README with Travis CI configuration detail
- [#311](https://github.com/php-coveralls/php-coveralls/pull/311) Update version in master to latest release
## 2.4.3
### Bug fix
- [#308](https://github.com/php-coveralls/php-coveralls/pull/308) Add file missing for PHP 8
### Miscellaneous
- [#303](https://github.com/php-coveralls/php-coveralls/pull/303) Update README.md
## 2.4.2
### Bug fix
- [#302](https://github.com/php-coveralls/php-coveralls/pull/302) Add COVERALLS_REPO_TOKEN to error message when run on Github Action
- [#299](https://github.com/php-coveralls/php-coveralls/pull/299) Correct spelling error in Github
## 2.4.1
### Bug fix
- [#298](https://github.com/php-coveralls/php-coveralls/pull/298) Fix support of branch name with hyphen and "(no branch)"
## 2.4.0
### Enhancement
- [#296](https://github.com/php-coveralls/php-coveralls/pull/296) Add Github Actions and COVERALLS_FLAG_NAME support
- [#295](https://github.com/php-coveralls/php-coveralls/pull/295) Add support for PHP 8 + PHPUnit 9
- [#289](https://github.com/php-coveralls/php-coveralls/pull/289) Add insecure option
### Miscellaneous
- [#297](https://github.com/php-coveralls/php-coveralls/pull/297) DX: .gitignore cache PHPUnit
## 2.3.0
### Enhancement
- [#290](https://github.com/php-coveralls/php-coveralls/pull/290) Allow to specify endpoint in arguments
- [#288](https://github.com/php-coveralls/php-coveralls/pull/288) Add Guzzle 7 support
- [#279](https://github.com/php-coveralls/php-coveralls/pull/279) Added COVERALLS_PARALLEL support and Configured CI_BUILD_NUMBER for Travis CI
### Miscellaneous
- [#294](https://github.com/php-coveralls/php-coveralls/pull/294) DX: Allow PHPUnit 7
- [#292](https://github.com/php-coveralls/php-coveralls/pull/292) CI: reduce amount of jobs
- [#291](https://github.com/php-coveralls/php-coveralls/pull/291) DX: Configurator - reduce cyclomatic complexity
- [#286](https://github.com/php-coveralls/php-coveralls/pull/286) Fix incorrect version in README
- [#283](https://github.com/php-coveralls/php-coveralls/pull/283) Update .travis.yml to include PHP 7.4
## 2.2.0
### Enhancement
- [#269](https://github.com/php-coveralls/php-coveralls/pull/269) Add possibility to change entry point
### Miscellaneous
- [#277](https://github.com/php-coveralls/php-coveralls/pull/277) DX: Update PHP CS Fixer
- [#276](https://github.com/php-coveralls/php-coveralls/pull/276) DX: Fix PHPMd config, allowing build to pass
- [#274](https://github.com/php-coveralls/php-coveralls/pull/274) Allow Symfony 5
- [#268](https://github.com/php-coveralls/php-coveralls/pull/268) Update minimum version of symfony/yaml to 2.0.5
- [#267](https://github.com/php-coveralls/php-coveralls/pull/267) Add --dev to install step
## 2.1.0
### Enhancement
- [#263](https://github.com/php-coveralls/php-coveralls/pull/263) JsonFile - detect json_encode failure
- [#262](https://github.com/php-coveralls/php-coveralls/pull/262) DX: Improve error messages
- [#261](https://github.com/php-coveralls/php-coveralls/pull/261) Return non-0 status on command errors
### Miscellaneous
- [#265](https://github.com/php-coveralls/php-coveralls/pull/265) Remove obsolete apigen and versioneye
- [#264](https://github.com/php-coveralls/php-coveralls/pull/264) Drop HHVM support
- [#256](https://github.com/php-coveralls/php-coveralls/pull/256) Update references to renamed binary
## 2.0.0
### Bug fix
- [#232](https://github.com/php-coveralls/php-coveralls/pull/232) phar building - set up platform.php for composer before building phar file
### Enhancement
- [#223](https://github.com/php-coveralls/php-coveralls/pull/223) Make project works on Windows
### Miscellaneous
- Binary and phar renamed to match tool name: php-coveralls and php-coveralls.phar
- [#228](https://github.com/php-coveralls/php-coveralls/pull/228) Rename vendor
- [#227](https://github.com/php-coveralls/php-coveralls/pull/227) Drop V1 from namespaces and class names
- Upgrade to Guzzle 6
## 1.1.0
### Enhancement
- [#192](https://github.com/php-coveralls/php-coveralls/pull/192) let output json path be configurable
## 1.0.2
### Miscellaneous
- Update github repo link
- [#250](https://github.com/php-coveralls/php-coveralls/pull/250) GitCommand - drop useless tests
- [#248](https://github.com/php-coveralls/php-coveralls/pull/248) Allow Symfony 4
- [#249](https://github.com/php-coveralls/php-coveralls/pull/249) Allow PHPUnit 6
- [#224](https://github.com/php-coveralls/php-coveralls/pull/224) Travis - update used ubuntu dist
- [#212](https://github.com/php-coveralls/php-coveralls/pull/212) update PHP CS Fixer
- Use stable and semantic version constrains
- Start v1.0.2 development
- Phpdoc
## 1.0.1
### Miscellaneous
- [#183](https://github.com/php-coveralls/php-coveralls/pull/183) Lower required version of symfony/*
## 1.0.0
### Miscellaneous
- [#136](https://github.com/php-coveralls/php-coveralls/pull/136) Removed src_dir from CoverallsConfiguration
- [#154](https://github.com/php-coveralls/php-coveralls/issues/154) Show a deprecation notice when src_dir is set in the config
## 0.7.0
### Bug fix
- [#30](https://github.com/php-coveralls/php-coveralls/issues/30) Fix bug: Guzzle\Common\Exception\RuntimeException occur without response body
- [#41](https://github.com/php-coveralls/php-coveralls/issues/41) CloverXmlCoverageCollector could not handle root directory
- [#114](https://github.com/php-coveralls/php-coveralls/pull/114) Fix PHP 5.3.3, Fix HHVM on Travis, boost Travis configuration, enhance PHP CS Fixer usage
### Enhancement
- [#15](https://github.com/php-coveralls/php-coveralls/issues/15) Support environment prop in json_file
- [#24](https://github.com/php-coveralls/php-coveralls/issues/24) Show helpful message if the requirements are not satisfied
- [#53](https://github.com/php-coveralls/php-coveralls/issues/53) Setting configuration options through command line flags
- Added --root_dir and --coverage_clover flags
- [#64](https://github.com/php-coveralls/php-coveralls/issues/64) file names need to be relative to the git repo root
- [#114](https://github.com/php-coveralls/php-coveralls/pull/114) Fix PHP 5.3.3, Fix HHVM on Travis, boost Travis configuration, enhance PHP CS Fixer usage
- [#124](https://github.com/php-coveralls/php-coveralls/pull/124) Create a .phar file
- [#149](https://github.com/php-coveralls/php-coveralls/pull/149) Build phar file on travis
- [#127](https://github.com/php-coveralls/php-coveralls/issues/126) Remove src_dir entirely
### Miscellaneous
- [#17](https://github.com/php-coveralls/php-coveralls/issues/17) Refactor test cases
- [#32](https://github.com/php-coveralls/php-coveralls/issues/32) Refactor CoverallsV1JobsCommand
- [#35](https://github.com/php-coveralls/php-coveralls/issues/35) Remove ext-curl dependency
- [#38](https://github.com/php-coveralls/php-coveralls/issues/38) Change namespace
- [#114](https://github.com/php-coveralls/php-coveralls/pull/114) PHP 7.0.0 is now officially supported
## 0.6.1 (2013-05-04)
### Bug fix
- [#27](https://github.com/php-coveralls/php-coveralls/issues/27) Fix bug: Response message is not shown if exception occurred
### Enhancement
- [#23](https://github.com/php-coveralls/php-coveralls/issues/23) Add CLI option: `--exclude-no-stmt`
- [#23](https://github.com/php-coveralls/php-coveralls/issues/23) Add .coveralls.yml configuration: `exclude_no_stmt`
## 0.6.0 (2013-05-03)
### Bug fix
- Fix bug: Show exception log at sending a request instead of exception backtrace
- [#12](https://github.com/php-coveralls/php-coveralls/issues/12) Fix bug: end of file should not be included in code coverage
- [#21](https://github.com/php-coveralls/php-coveralls/issues/21) Fix bug: add connection error handling
### Enhancement
- [#11](https://github.com/php-coveralls/php-coveralls/issues/11) Support configuration for multiple clover.xml
- [#14](https://github.com/php-coveralls/php-coveralls/issues/14) Log enhancement
- show file size of `json_file`
- show number of included source files
- show elapsed time and memory usage
- show coverage
- show response message
- [#18](https://github.com/php-coveralls/php-coveralls/issues/18) Relax dependent libs version
## 0.5.0 (2013-04-29)
### Bug fix
- Fix bug: only existing file lines should be included in coverage data
### Enhancement
- `--verbose (-v)` CLI option enables logging
- Support standardized env vars ([Codeship](https://www.codeship.io) supported these env vars)
- CI_NAME
- CI_BUILD_NUMBER
- CI_BUILD_URL
- CI_BRANCH
- CI_PULL_REQUEST
### Miscellaneous
- Refactor console logging (PSR-3 compliant)
- Change composer's minimal stability from dev to stable
## 0.4.0 (2013-04-21)
### Bug fix
- Fix bug: `repo_token` is required on CircleCI, Jenkins
### Enhancement
- Replace REST client implementation by [guzzle/guzzle](https://github.com/guzzle/guzzle)
## 0.3.2 (2013-04-20)
### Bug fix
- Fix bug: API reqest from local environment should be with repo_token
- Fix bug: service_name in .coveralls.yml will not reflect to json_file
## 0.3.1 (2013-04-19)
### Bug fix
- [#1](Installing with composer issue) Fix bug: wrong bin path of composer.json ([@zomble](https://github.com/zomble)).
## 0.3.0 (2013-04-19)
### Enhancement
- Better CLI implementation by using [symfony/Console](https://github.com/symfony/Console) component
- Support `--dry-run`, `--config (-c)` CLI option
## 0.2.0 (2013-04-18)
### Enhancement
- Support .coveralls.yml
## 0.1.0 (2013-04-15)
First release.
- Support Travis CI (tested)
- Implement CircleCI, Jenkins, local environment (but not tested on these CI environments)
- Collect coverage information from clover.xml
- Collect git repository information
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2013 Kitamura Satoshi
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,379 @@
php-coveralls
=============
[![Static Code Analysis](https://github.com/php-coveralls/php-coveralls/actions/workflows/static-code-analysis.yml/badge.svg)](https://github.com/php-coveralls/php-coveralls/actions/workflows/static-code-analysis.yml)
[![CI](https://github.com/php-coveralls/php-coveralls/actions/workflows/ci.yml/badge.svg)](https://github.com/php-coveralls/php-coveralls/actions/workflows/ci.yml)
[![Coverage Status](https://coveralls.io/repos/php-coveralls/php-coveralls/badge.png?branch=master)](https://coveralls.io/r/php-coveralls/php-coveralls)
[![Latest Stable Version](https://poser.pugx.org/php-coveralls/php-coveralls/v/stable.png)](https://packagist.org/packages/php-coveralls/php-coveralls)
[![Total Downloads](https://poser.pugx.org/php-coveralls/php-coveralls/downloads.png)](https://packagist.org/packages/php-coveralls/php-coveralls)
PHP client library for [Coveralls](https://coveralls.io).
# Prerequisites
- PHP 5.5+ for 2.x or 5.3+ for 1.x
- On [GitHub](https://github.com/)
- Building on [Travis CI](http://travis-ci.org/), [CircleCI](https://circleci.com/), [Jenkins](http://jenkins-ci.org/), [Codeship](https://www.codeship.io/) or [GitHub Actions](https://github.com/features/actions)
- Testing by [PHPUnit](https://github.com/sebastianbergmann/phpunit/) or other testing framework that can generate clover style coverage report
# Installation
## Download phar file
We started to create a phar file, starting from the version 0.7.0
release. It is available at the URLs like:
```
https://github.com/php-coveralls/php-coveralls/releases/download/v2.6.0/php-coveralls.phar
```
Download the file and add exec permissions:
```sh
$ wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.6.0/php-coveralls.phar
$ chmod +x php-coveralls.phar
```
## Install by composer
To install php-coveralls with Composer, run the following command:
```sh
$ composer require --dev php-coveralls/php-coveralls
```
If you need support for PHP versions older than 5.5, you will need to use a 1.x version:
```sh
$ composer require --dev 'php-coveralls/php-coveralls:^1.1'
```
You can see this library on [Packagist](https://packagist.org/packages/php-coveralls/php-coveralls).
Composer installs autoloader at `./vendor/autoloader.php`. If you use
php-coveralls in your php script, add:
```php
require_once 'vendor/autoload.php';
```
If you use Symfony2, autoloader has to be detected automatically.
## Use it from your git clone
Or you can use git clone command:
```sh
# HTTP
$ git clone https://github.com/php-coveralls/php-coveralls.git
# SSH
$ git clone git@github.com:php-coveralls/php-coveralls.git
```
# Configuration
Currently php-coveralls supports clover style coverage report and collects coverage information from `clover.xml`.
## PHPUnit
Make sure that `phpunit.xml.dist` is configured to generate "coverage-clover" type log named `clover.xml` like the following configuration:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit ...>
<logging>
...
<log type="coverage-clover" target="build/logs/clover.xml"/>
...
</logging>
</phpunit>
```
You can also use `--coverage-clover` CLI option.
```sh
phpunit --coverage-clover build/logs/clover.xml
```
### phpcov
Above settings are good for most projects if your test suite is executed once a build and is not divided into several parts. But if your test suite is configured as parallel tasks or generates multiple coverage reports through a build, you can use either `coverage_clover` configuration in `.coveralls.yml` ([see below coverage clover configuration section](#coverage-clover-configuration)) to specify multiple `clover.xml` files or `phpcov` for processing coverage reports.
#### composer.json
```json
"require-dev": {
"php-coveralls/php-coveralls": "^2.6",
"phpunit/phpcov": "^2.0"
},
```
#### phpunit configuration
Make sure that `phpunit.xml.dist` is configured to generate "coverage-php" type log:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit ...>
<logging>
...
<log type="coverage-php" target="build/cov/coverage.cov"/>
...
</logging>
</phpunit>
```
You can also use `--coverage-php` CLI option.
```sh
# use --coverage-php option instead of --coverage-clover
phpunit --coverage-php build/cov/coverage-${component_name}.cov
```
#### phpcov configuration
And then, execute `phpcov.php` to merge `coverage.cov` logs.
```sh
# get information
php vendor/bin/phpcov.php --help
# merge coverage.cov logs under build/cov
php vendor/bin/phpcov.php merge --clover build/logs/clover.xml build/cov
# in case of memory exhausting error
php -d memory_limit=-1 vendor/bin/phpcov.php ...
```
### clover.xml
php-coveralls collects `count` attribute in a `line` tag from `clover.xml` if its `type` attribute equals to `stmt`. When `type` attribute equals to `method`, php-coveralls excludes its `count` attribute from coverage collection because abstract method in an abstract class is never counted though subclasses implement that method which is executed in test cases.
```xml
<!-- this one is counted as code coverage -->
<line num="37" type="stmt" count="1"/>
<!-- this one is not counted -->
<line num="43" type="method" name="getCommandName" crap="1" count="1"/>
```
## Travis CI
Add `php php-coveralls.phar` or `php vendor/bin/php-coveralls` to your `.travis.yml` at `after_success`.
```yml
# .travis.yml
language: php
php:
- 5.5
- 5.4
- 5.3
env:
global:
- XDEBUG_MODE=coverage
matrix:
allow_failures:
- php: 5.5
install:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install --dev --no-interaction
script:
- mkdir -p build/logs
- php vendor/bin/phpunit -c phpunit.xml.dist
after_success:
- travis_retry php vendor/bin/php-coveralls
# or enable logging
- travis_retry php vendor/bin/php-coveralls -v
```
## CircleCI
Enable Xdebug in your `circle.yml` at `dependencies` section since currently Xdebug extension is not pre-enabled. `composer` and `phpunit` are pre-installed but you can install them manually in this dependencies section. The following sample uses default ones.
```yml
machine:
php:
version: 5.4.10
## Customize dependencies
dependencies:
override:
- mkdir -p build/logs
- composer install --dev --no-interaction
- sed -i 's/^;//' ~/.phpenv/versions/$(phpenv global)/etc/conf.d/xdebug.ini
## Customize test commands
test:
override:
- phpunit -c phpunit.xml.dist
```
Add `COVERALLS_REPO_TOKEN` environment variable with your coveralls repo token on Web UI (Tweaks -> Environment Variable).
## Codeship
You can configure CI process for Coveralls by adding the following commands to the textarea on Web UI (Project settings > Test tab).
In the "Modify your Setup Commands" section:
```sh
curl -s http://getcomposer.org/installer | php
php composer.phar install --dev --no-interaction
mkdir -p build/logs
```
In the "Modify your Test Commands" section:
```sh
php vendor/bin/phpunit -c phpunit.xml.dist
php vendor/bin/php-coveralls
```
Next, open Project settings > Environment tab, you can set `COVERALLS_REPO_TOKEN` environment variable.
In the "Configure your environment variables" section:
```sh
COVERALLS_REPO_TOKEN=your_token
```
## GitHub Actions
Add a new step after phpunit generate coverage report.
```yaml
- name: Upload coverage results to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
composer global require php-coveralls/php-coveralls
php-coveralls --coverage_clover=build/logs/clover.xml -v
```
## From local environment
If you would like to call Coveralls API from your local environment, you can set `COVERALLS_RUN_LOCALLY` environment variable. This configuration requires `repo_token` to specify which project on Coveralls your project maps to. This can be done by configuring `.coveralls.yml` or `COVERALLS_REPO_TOKEN` environment variable.
```sh
$ export COVERALLS_RUN_LOCALLY=1
# either env var
$ export COVERALLS_REPO_TOKEN=your_token
# or .coveralls.yml configuration
$ vi .coveralls.yml
repo_token: your_token # should be kept secret!
```
php-coveralls set the following properties to `json_file` which is sent to Coveralls API (same behaviour as the Ruby library will do except for the service name).
- service_name: php-coveralls
- service_event_type: manual
## Parallel Builds
Coveralls provides the ability to combine coverage result from multiple parallel builds into one. To enable the feature you can set the following in your environment variable.
```sh
COVERALLS_PARALLEL=true
```
To distinguish your job name, please set the `COVERALLS_FLAG_NAME` environment variable.
```sh
COVERALLS_FLAG_NAME=$YOUR_PHP_VERSION
```
Bear in mind that you will need to configure your build to send a webhook after all the parallel builds are done in order for Coveralls to merge the results.
Refer to [Parallel Builds Webhook](https://docs.coveralls.io/parallel-build-webhook) for more information for setup on your environment.
## CLI options
You can get help information for `coveralls` with the `--help (-h)` option.
```sh
php vendor/bin/php-coveralls --help
```
- `--config (-c)`: Used to specify the path to `.coveralls.yml`. Default is `.coveralls.yml`
- `--verbose (-v)`: Used to show logs.
- `--dry-run`: Used not to send json_file to Coveralls Jobs API.
- `--exclude-no-stmt`: Used to exclude source files that have no executable statements.
- `--env (-e)`: Runtime environment name: test, dev, prod (default: "prod")
- `--coverage_clover (-x)`: Coverage clover xml files(allowing multiple values)
- `--json_path` (-o): Used to specify where to output json_file that will be
uploaded to Coveralls API. (default: `build/logs/coveralls-upload.json`)
- `--root_dir (-r)`: Root directory of the project. (default: ".")
## .coveralls.yml
php-coveralls can use optional `.coveralls.yml` file to configure options. This configuration file is usually at the root level of your repository, but you can specify other path by `--config (or -c)` CLI option. Following options are the same as Ruby library ([see reference on coveralls.io](https://coveralls.io/docs/ruby)).
- `repo_token`: Used to specify which project on Coveralls your project maps to. This is only needed for repos not using CI and should be kept secret
- `service_name`: Allows you to specify where Coveralls should look to find additional information about your builds. This can be any string, but using `travis-ci` or `travis-pro` will allow Coveralls to fetch branch data, comment on pull requests, and more.
Following options can be used for php-coveralls.
- `entry_point`: Used to specify API endpoint for sending reports. Useful when using self-hosted coveralls or other other, similar service (eg opencov). Default is `https://coveralls.io`.
- `coverage_clover`: Used to specify the path to `clover.xml`. Default is `build/logs/clover.xml`
- `json_path`: Used to specify where to output `json_file` that will be uploaded to Coveralls API. Default is `build/logs/coveralls-upload.json`.
```yml
# .coveralls.yml example configuration
# same as Ruby lib
repo_token: your_token # should be kept secret!
service_name: travis-pro # travis-ci or travis-pro
# for php-coveralls
coverage_clover: build/logs/clover.xml
json_path: build/logs/coveralls-upload.json
```
### coverage clover configuration
You can specify multiple `clover.xml` logs at `coverage_clover`. This is useful for a project that has more than two test suites if all of the test results should be merged into one `json_file`.
```yml
#.coveralls.yml
# single file
coverage_clover: build/logs/clover.xml
# glob
coverage_clover: build/logs/clover-*.xml
# array
# specify files
coverage_clover:
- build/logs/clover-Auth.xml
- build/logs/clover-Db.xml
- build/logs/clover-Validator.xml
```
You can also use `--coverage_clover` (or `-x`) command line option as follows:
```
coveralls --coverage_clover=build/logs/my-clover.xml
```
### root_dir detection and override
This tool assume the current directory is the project root directory by default. You can override it with `--root_dir` command line option.
# Change log
[See changelog](CHANGELOG.md)
# Wiki
[See wiki](https://github.com/php-coveralls/php-coveralls/wiki)
@@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php
if (\file_exists(__DIR__ . '/../vendor/autoload.php')) {
// Called from local git clone.
require __DIR__ . '/../vendor/autoload.php';
} elseif (\file_exists(__DIR__ . '/../../../autoload.php')) {
// Called from your project's vendor dir.
require __DIR__ . '/../../../autoload.php';
} else {
die(
'You need to set up the project dependencies using the following commands:' . PHP_EOL .
'curl -s http://getcomposer.org/installer | php' . PHP_EOL .
'php composer.phar install' . PHP_EOL
);
}
use PhpCoveralls\Bundle\CoverallsBundle\Console\ApplicationFactory;
$app = ApplicationFactory::create(\getcwd());
$app->run();
@@ -0,0 +1,27 @@
{
"alias": "php-coveralls.phar",
"base-path": null,
"chmod": "0755",
"directories": [
"bin",
"src"
],
"check-requirements": false,
"compactors": [
"KevinGH\\Box\\Compactor\\Php"
],
"files": [
"LICENSE"
],
"finder": [
{
"name": ["*.php", "*.php8", "*.pem"],
"exclude": ["tests"],
"in": ["vendor"]
}
],
"git-version": "package_version",
"main": "bin/php-coveralls",
"output": "build/artifacts/php-coveralls.phar",
"stub": true
}
@@ -0,0 +1,16 @@
machine:
php:
version:
7.1.0
dependencies:
override:
- mkdir -p build/logs
- echo "date.timezone = America/Los_Angeles" > /opt/circleci/php/$(phpenv global)/etc/conf.d/date.ini
- composer update --prefer-stable --prefer-lowest --no-interaction
test:
override:
- phpunit
post:
- bin/php-coveralls -v --exclude-no-stmt
@@ -0,0 +1,77 @@
{
"name": "php-coveralls/php-coveralls",
"description": "PHP client library for Coveralls API",
"keywords": ["coverage", "github", "test", "ci"],
"homepage": "https://github.com/php-coveralls/php-coveralls",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Kitamura Satoshi",
"email": "with.no.parachute@gmail.com",
"homepage": "https://www.facebook.com/satooshi.jp",
"role": "Original creator"
},
{
"name": "Takashi Matsuo",
"email": "tmatsuo@google.com"
},
{
"name": "Google Inc"
},
{
"name": "Dariusz Ruminski",
"email": "dariusz.ruminski@gmail.com",
"homepage": "https://github.com/keradus"
},
{
"name": "Contributors",
"homepage": "https://github.com/php-coveralls/php-coveralls/graphs/contributors"
}
],
"require": {
"php": "^5.5 || ^7.0 || ^8.0",
"ext-json": "*",
"ext-simplexml": "*",
"guzzlehttp/guzzle": "^6.0 || ^7.0",
"psr/log": "^1.0 || ^2.0",
"symfony/config": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
"symfony/console": "^2.1 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
"symfony/stopwatch": "^2.0 || ^3.0 || ^4.0 || ^5.0 || ^6.0",
"symfony/yaml": "^2.0.5 || ^3.0 || ^4.0 || ^5.0 || ^6.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.4.3 || ^6.0 || ^7.0 || >=8.0 <8.5.29 || >=9.0 <9.5.23",
"sanmai/phpunit-legacy-adapter": "^6.1 || ^8.0"
},
"suggest": {
"symfony/http-kernel": "Allows Symfony integration"
},
"autoload": {
"psr-4": {
"PhpCoveralls\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"PhpCoveralls\\Tests\\": "tests/"
}
},
"bin": [
"bin/php-coveralls"
],
"scripts": {
"install-dev-tools": [
"cd dev-tools && composer install --no-interaction"
],
"sca": [
"php dev-tools/vendor/bin/php-cs-fixer fix --dry-run -vv",
"php dev-tools/vendor/bin/phpmd src text build/config/phpmd.xml"
]
},
"config": {
"optimize-autoloader": true,
"process-timeout": 0,
"sort-packages": true
}
}
@@ -0,0 +1,19 @@
{
"require": {
"php": "^8.1",
"ext-json": "*",
"ext-simplexml": "*"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.21.1",
"pdepend/pdepend": "^2.2",
"phpmd/phpmd": "^2.3",
"sebastian/finder-facade": "^1.1",
"sebastian/phpcpd": "^1.4",
"theseer/fdomdocument": "^1.6"
},
"config": {
"optimize-autoloader": true,
"sort-packages": true
}
}
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutChangesToGlobalState="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestsThatDoNotTestAnything="true"
bootstrap="vendor/autoload.php"
colors="true"
columns="max"
convertDeprecationsToExceptions="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
stopOnRisky="false"
verbose="true"
>
<php>
<ini name="error_reporting" value="E_ALL" />
</php>
<testsuites>
<testsuite name="PHP Coveralls component test suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src</directory>
<exclude>
<directory>./build</directory>
<directory>./composer</directory>
<directory>./tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="build/logs/clover.xml"/>
<log type="junit" target="build/logs/junit.xml" logIncompleteSkipped="false"/>
</logging>
</phpunit>
@@ -0,0 +1,76 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Api;
use GuzzleHttp\Client;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration;
/**
* Coveralls API client.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
abstract class CoverallsApi
{
/**
* Configuration.
*
* @var Configuration
*/
protected $config;
/**
* HTTP client.
*
* @var \GuzzleHttp\Client
*/
protected $client;
/**
* Constructor.
*
* @param Configuration $config configuration
* @param \GuzzleHttp\Client $client hTTP client
*/
public function __construct(Configuration $config, Client $client = null)
{
$this->config = $config;
$this->client = $client;
}
// accessor
/**
* Return configuration.
*
* @return Configuration
*/
public function getConfiguration()
{
return $this->config;
}
/**
* Set HTTP client.
*
* @param \GuzzleHttp\Client $client hTTP client
*
* @return $this
*/
public function setHttpClient(Client $client)
{
$this->client = $client;
return $this;
}
/**
* Return HTTP client.
*
* @return \GuzzleHttp\Client
*/
public function getHttpClient()
{
return $this->client;
}
}
@@ -0,0 +1,190 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Api;
use PhpCoveralls\Bundle\CoverallsBundle\Collector\CiEnvVarsCollector;
use PhpCoveralls\Bundle\CoverallsBundle\Collector\CloverXmlCoverageCollector;
use PhpCoveralls\Bundle\CoverallsBundle\Collector\GitInfoCollector;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile;
use PhpCoveralls\Component\System\Git\GitCommand;
/**
* Jobs API.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class Jobs extends CoverallsApi
{
/**
* URL for jobs API.
*
* @var string
*/
const URL = '/api/v1/jobs';
/**
* Filename as a POST parameter.
*
* @var string
*/
const FILENAME = 'json_file';
/**
* JsonFile.
*
* @var JsonFile
*/
protected $jsonFile;
// API
/**
* Collect clover XML into json_file.
*
* @return $this
*/
public function collectCloverXml()
{
$rootDir = $this->config->getRootDir();
$cloverXmlPaths = $this->config->getCloverXmlPaths();
$xmlCollector = new CloverXmlCoverageCollector();
foreach ($cloverXmlPaths as $cloverXmlPath) {
$xml = simplexml_load_file($cloverXmlPath);
$xmlCollector->collect($xml, $rootDir);
}
$this->jsonFile = $xmlCollector->getJsonFile();
if ($this->config->isExcludeNoStatements()) {
$this->jsonFile->excludeNoStatementsFiles();
}
$this->jsonFile->sortSourceFiles();
return $this;
}
/**
* Collect git repository info into json_file.
*
* @return $this
*/
public function collectGitInfo()
{
$command = new GitCommand();
$gitCollector = new GitInfoCollector($command);
$this->jsonFile->setGit($gitCollector->collect());
return $this;
}
/**
* Collect environment variables.
*
* @param array $env $_SERVER environment
*
* @return $this
*
* @throws \PhpCoveralls\Bundle\CoverallsBundle\Entity\Exception\RequirementsNotSatisfiedException
*/
public function collectEnvVars(array $env)
{
$envCollector = new CiEnvVarsCollector($this->config);
try {
$this->jsonFile->fillJobs($envCollector->collect($env));
} catch (\PhpCoveralls\Bundle\CoverallsBundle\Entity\Exception\RequirementsNotSatisfiedException $e) {
$e->setReadEnv($envCollector->getReadEnv());
throw $e;
}
return $this;
}
/**
* Dump uploading json file.
*
* @return $this
*/
public function dumpJsonFile()
{
$jsonPath = $this->config->getJsonPath();
file_put_contents($jsonPath, $this->jsonFile);
return $this;
}
/**
* Send json_file to jobs API.
*
* @return null|\GuzzleHttp\Psr7\Response
*/
public function send()
{
if ($this->config->isDryRun()) {
return;
}
$url = $this->config->getEntryPoint() . static::URL;
$jsonPath = $this->config->getJsonPath();
return $this->upload($url, $jsonPath, static::FILENAME);
}
// accessor
/**
* Set JsonFile.
*
* @param JsonFile $jsonFile json_file content
*
* @return $this
*/
public function setJsonFile(JsonFile $jsonFile)
{
$this->jsonFile = $jsonFile;
return $this;
}
/**
* Return JsonFile.
*
* @return null|JsonFile
*/
public function getJsonFile()
{
return $this->jsonFile;
}
// internal method
/**
* Upload a file.
*
* @param string $url uRL to upload
* @param string $path file path
* @param string $filename filename
*
* @return \GuzzleHttp\Psr7\Response
*/
protected function upload($url, $path, $filename)
{
$options = [
'multipart' => [
[
'name' => $filename,
'contents' => file_get_contents($path),
'filename' => basename($path),
],
],
];
return $this->client->post($url, $options);
}
}
@@ -0,0 +1,292 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Collector;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration;
/**
* Environment variables collector for CI envrionment.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class CiEnvVarsCollector
{
/**
* Configuration.
*
* @var Configuration
*/
protected $config;
/**
* Environment variables.
*
* Overwritten through collection process.
*
* @var array
*/
protected $env;
/**
* Read environment variables.
*
* @var array
*/
protected $readEnv;
/**
* Constructor.
*
* @param Configuration $config configuration
*/
public function __construct(Configuration $config)
{
$this->config = $config;
}
// API
/**
* Collect environment variables.
*
* @param array $env $_SERVER environment
*
* @return array
*/
public function collect(array $env)
{
$this->env = $env;
$this->readEnv = [];
$this
->fillTravisCi()
->fillCircleCi()
->fillAppVeyor()
->fillJenkins()
->fillGithubActions()
->fillLocal()
->fillRepoToken()
->fillParallel()
;
return $this->env;
}
// accessor
/**
* Return read environment variables.
*
* @return array
*/
public function getReadEnv()
{
return $this->readEnv;
}
// internal method
/**
* Fill Travis CI environment variables.
*
* "TRAVIS", "TRAVIS_BUILD_NUMBER", TRAVIS_JOB_ID" must be set.
*
* @return $this
*/
protected function fillTravisCi()
{
if (isset($this->env['TRAVIS']) && $this->env['TRAVIS'] && isset($this->env['TRAVIS_JOB_ID'], $this->env['TRAVIS_BUILD_NUMBER'])) {
$this->env['CI_JOB_ID'] = $this->env['TRAVIS_JOB_ID'];
$this->env['CI_BUILD_NUMBER'] = $this->env['TRAVIS_BUILD_NUMBER'];
if ($this->config->hasServiceName()) {
$this->env['CI_NAME'] = $this->config->getServiceName();
} else {
$this->env['CI_NAME'] = 'travis-ci';
}
// backup
$this->readEnv['TRAVIS'] = $this->env['TRAVIS'];
$this->readEnv['TRAVIS_JOB_ID'] = $this->env['TRAVIS_JOB_ID'];
$this->readEnv['CI_NAME'] = $this->env['CI_NAME'];
$this->readEnv['CI_BUILD_NUMBER'] = $this->env['CI_BUILD_NUMBER'];
}
return $this;
}
/**
* Fill Github Actions environment variables.
*
* @return $this
*/
protected function fillGithubActions()
{
if (!isset($this->env['GITHUB_ACTIONS'])) {
return $this;
}
$this->env['CI_NAME'] = 'github';
$githubEventName = $this->env['GITHUB_EVENT_NAME'];
$githubRef = $this->env['GITHUB_REF'];
if (strpos($githubRef, 'refs/heads/') !== false) {
$githubRef = str_replace('refs/heads/', '', $githubRef);
} elseif ($githubEventName === 'pull_request') {
$refParts = explode('/', $githubRef);
$prNumber = $refParts[2];
$this->env['CI_PULL_REQUEST'] = $prNumber;
$this->readEnv['CI_PULL_REQUEST'] = $this->env['CI_PULL_REQUEST'];
} elseif (strpos($githubRef, 'refs/tags/') !== false) {
$githubRef = str_replace('refs/tags/', '', $githubRef);
}
// Same as coverallsapp/github-action
// @link https://github.com/coverallsapp/github-action/blob/5984097c6e76d873ef1d8e8e1836b0914d307c3c/src/run.ts#L47
$this->env['CI_JOB_ID'] = $this->env['GITHUB_RUN_ID'];
$this->env['CI_BRANCH'] = $githubRef;
$this->readEnv['GITHUB_ACTIONS'] = $this->env['GITHUB_ACTIONS'];
$this->readEnv['GITHUB_REF'] = $this->env['GITHUB_REF'];
$this->readEnv['CI_NAME'] = $this->env['CI_NAME'];
$this->readEnv['CI_JOB_ID'] = $this->env['CI_JOB_ID'];
$this->readEnv['CI_BRANCH'] = $this->env['CI_BRANCH'];
return $this;
}
/**
* Fill CircleCI environment variables.
*
* "CIRCLECI", "CIRCLE_WORKFLOW_ID" must be set.
*
* @return $this
*/
protected function fillCircleCi()
{
if (isset($this->env['CIRCLECI']) && $this->env['CIRCLECI'] && isset($this->env['CIRCLE_WORKFLOW_ID'])) {
$this->env['CI_BUILD_NUMBER'] = $this->env['CIRCLE_WORKFLOW_ID'];
$this->env['CI_NAME'] = 'circleci';
// backup
$this->readEnv['CIRCLECI'] = $this->env['CIRCLECI'];
$this->readEnv['CIRCLE_WORKFLOW_ID'] = $this->env['CIRCLE_WORKFLOW_ID'];
$this->readEnv['CI_NAME'] = $this->env['CI_NAME'];
}
return $this;
}
/**
* Fill AppVeyor environment variables.
*
* "APPVEYOR", "APPVEYOR_BUILD_NUMBER" must be set.
*
* @return $this
*/
protected function fillAppVeyor()
{
if (isset($this->env['APPVEYOR']) && $this->env['APPVEYOR'] && isset($this->env['APPVEYOR_BUILD_NUMBER'])) {
$this->env['CI_BUILD_NUMBER'] = $this->env['APPVEYOR_BUILD_NUMBER'];
$this->env['CI_JOB_ID'] = $this->env['APPVEYOR_JOB_NUMBER'];
$this->env['CI_BRANCH'] = $this->env['APPVEYOR_REPO_BRANCH'];
$this->env['CI_PULL_REQUEST'] = $this->env['APPVEYOR_PULL_REQUEST_NUMBER'];
$this->env['CI_NAME'] = 'AppVeyor';
// backup
$this->readEnv['APPVEYOR'] = $this->env['APPVEYOR'];
$this->readEnv['APPVEYOR_BUILD_NUMBER'] = $this->env['APPVEYOR_BUILD_NUMBER'];
$this->readEnv['APPVEYOR_JOB_NUMBER'] = $this->env['APPVEYOR_JOB_NUMBER'];
$this->readEnv['APPVEYOR_REPO_BRANCH'] = $this->env['APPVEYOR_REPO_BRANCH'];
$this->readEnv['APPVEYOR_PULL_REQUEST_NUMBER'] = $this->env['APPVEYOR_PULL_REQUEST_NUMBER'];
$this->readEnv['CI_NAME'] = $this->env['CI_NAME'];
}
return $this;
}
/**
* Fill Jenkins environment variables.
*
* "JENKINS_URL", "BUILD_NUMBER" must be set.
*
* @return $this
*/
protected function fillJenkins()
{
if (isset($this->env['JENKINS_URL'], $this->env['BUILD_NUMBER'])) {
$this->env['CI_BUILD_NUMBER'] = $this->env['BUILD_NUMBER'];
$this->env['CI_BUILD_URL'] = $this->env['JENKINS_URL'];
$this->env['CI_NAME'] = 'jenkins';
// backup
$this->readEnv['BUILD_NUMBER'] = $this->env['BUILD_NUMBER'];
$this->readEnv['JENKINS_URL'] = $this->env['JENKINS_URL'];
$this->readEnv['CI_NAME'] = $this->env['CI_NAME'];
}
return $this;
}
/**
* Fill local environment variables.
*
* "COVERALLS_RUN_LOCALLY" must be set.
*
* @return $this
*/
protected function fillLocal()
{
if (isset($this->env['COVERALLS_RUN_LOCALLY']) && $this->env['COVERALLS_RUN_LOCALLY']) {
$this->env['CI_JOB_ID'] = null;
$this->env['CI_NAME'] = 'php-coveralls';
$this->env['COVERALLS_EVENT_TYPE'] = 'manual';
// backup
$this->readEnv['COVERALLS_RUN_LOCALLY'] = $this->env['COVERALLS_RUN_LOCALLY'];
$this->readEnv['COVERALLS_EVENT_TYPE'] = $this->env['COVERALLS_EVENT_TYPE'];
$this->readEnv['CI_NAME'] = $this->env['CI_NAME'];
}
return $this;
}
/**
* Fill repo_token for unsupported CI service.
*
* "COVERALLS_REPO_TOKEN" must be set.
*
* @return $this
*/
protected function fillRepoToken()
{
if ($this->config->hasRepoToken()) {
$this->env['COVERALLS_REPO_TOKEN'] = $this->config->getRepoToken();
}
// backup
if (isset($this->env['COVERALLS_REPO_TOKEN'])) {
$this->readEnv['COVERALLS_REPO_TOKEN'] = $this->env['COVERALLS_REPO_TOKEN'];
}
return $this;
}
/**
* Fill parallel for parallel jobs.
*
* @return $this
*/
protected function fillParallel()
{
if (isset($this->env['COVERALLS_PARALLEL'])) {
$this->readEnv['COVERALLS_PARALLEL'] = $this->env['COVERALLS_PARALLEL'];
}
if (isset($this->env['COVERALLS_FLAG_NAME'])) {
$this->readEnv['COVERALLS_FLAG_NAME'] = $this->env['COVERALLS_FLAG_NAME'];
}
return $this;
}
}
@@ -0,0 +1,146 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Collector;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile;
/**
* Coverage collector for clover.xml.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class CloverXmlCoverageCollector
{
/**
* JsonFile.
*
* @var \PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile
*/
protected $jsonFile;
// API
/**
* Collect coverage from XML object.
*
* @param \SimpleXMLElement $xml clover XML object
* @param string $rootDir path to repository root directory
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile
*/
public function collect(\SimpleXMLElement $xml, $rootDir)
{
$root = rtrim($rootDir, \DIRECTORY_SEPARATOR) . \DIRECTORY_SEPARATOR;
if ($this->jsonFile === null) {
$this->jsonFile = new JsonFile();
}
// overwrite if run_at has already been set
$runAt = $this->collectRunAt($xml);
$this->jsonFile->setRunAt($runAt);
$xpaths = [
'/coverage/project/file',
'/coverage/project/package/file',
];
foreach ($xpaths as $xpath) {
foreach ($xml->xpath($xpath) as $file) {
$srcFile = $this->collectFileCoverage($file, $root);
if ($srcFile !== null) {
$this->jsonFile->addSourceFile($srcFile);
}
}
}
return $this->jsonFile;
}
// accessor
/**
* Return json file.
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile
*/
public function getJsonFile()
{
return $this->jsonFile;
}
// Internal method
/**
* Collect timestamp when the job ran.
*
* @param \SimpleXMLElement $xml clover XML object of a file
* @param string $format dateTime format
*
* @return string
*/
protected function collectRunAt(\SimpleXMLElement $xml, $format = 'Y-m-d H:i:s O')
{
$timestamp = $xml->project['timestamp'];
$runAt = new \DateTime('@' . $timestamp);
return $runAt->format($format);
}
/**
* Collect coverage data of a file.
*
* @param \SimpleXMLElement $file clover XML object of a file
* @param string $root path to src directory
*
* @return null|\PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile
*/
protected function collectFileCoverage(\SimpleXMLElement $file, $root)
{
$absolutePath = realpath((string) ($file['path'] ?: $file['name']));
if (strpos($absolutePath, $root) === false) {
return;
}
$filename = $absolutePath;
if ($root !== \DIRECTORY_SEPARATOR) {
$filename = str_replace($root, '', $absolutePath);
}
return $this->collectCoverage($file, $absolutePath, $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 \PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile
*/
protected function collectCoverage(\SimpleXMLElement $file, $path, $filename)
{
if ($this->jsonFile->hasSourceFile($path)) {
$srcFile = $this->jsonFile->getSourceFile($path);
} else {
$srcFile = new SourceFile($path, $filename);
}
foreach ($file->line as $line) {
if ((string) $line['type'] === 'stmt') {
$lineNum = (int) $line['num'];
if ($lineNum > 0) {
$srcFile->addCoverage($lineNum - 1, (int) $line['count']);
}
}
}
return $srcFile;
}
}
@@ -0,0 +1,163 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Collector;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Remote;
use PhpCoveralls\Component\System\Git\GitCommand;
/**
* Git repository info collector.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class GitInfoCollector
{
/**
* Git command.
*
* @var GitCommand
*/
protected $command;
/**
* Constructor.
*
* @param GitCommand $command Git command
*/
public function __construct(GitCommand $command)
{
$this->command = $command;
}
// API
/**
* Collect git repository info.
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Git
*/
public function collect()
{
$branch = $this->collectBranch();
$commit = $this->collectCommit();
$remotes = $this->collectRemotes();
return new Git($branch, $commit, $remotes);
}
// accessor
/**
* Return git command.
*
* @return \PhpCoveralls\Component\System\Git\GitCommand
*/
public function getCommand()
{
return $this->command;
}
// internal method
/**
* Collect branch name.
*
* @return string
*
* @throws \RuntimeException
*/
protected function collectBranch()
{
$branchesResult = $this->command->getBranches();
foreach ($branchesResult as $result) {
if ($result === '* (no branch)') {
// Case detected on Travis PUSH hook for tags, can be reporduced by following command:
// $ git clone --depth=1 --branch=v2.4.0 https://github.com/php-coveralls/php-coveralls.git php-coveralls && cd php-coveralls && git branch
// * (no branch)
return '(no branch)';
}
if (strpos($result, '* ') === 0) {
preg_match('/^\* (?:\(HEAD detached at )?([\w\/\-]+)\)?/', $result, $matches);
return $matches[1];
}
}
throw new \RuntimeException();
}
/**
* Collect commit info.
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit
*
* @throws \RuntimeException
*/
protected function collectCommit()
{
$commitResult = $this->command->getHeadCommit();
if (\count($commitResult) !== 6 || array_keys($commitResult) !== range(0, 5)) {
throw new \RuntimeException();
}
$commit = new Commit();
return $commit
->setId($commitResult[0])
->setAuthorName($commitResult[1])
->setAuthorEmail($commitResult[2])
->setCommitterName($commitResult[3])
->setCommitterEmail($commitResult[4])
->setMessage($commitResult[5])
;
}
/**
* Collect remotes info.
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Remote[]
*
* @throws \RuntimeException
*/
protected function collectRemotes()
{
$remotesResult = $this->command->getRemotes();
if (\count($remotesResult) === 0) {
throw new \RuntimeException();
}
// parse command result
$results = [];
foreach ($remotesResult as $result) {
if (strpos($result, ' ') !== false) {
list($remote) = explode(' ', $result, 2);
$results[] = $remote;
}
}
// filter
$results = array_unique($results);
// create Remote instances
$remotes = [];
foreach ($results as $result) {
if (strpos($result, "\t") !== false) {
list($name, $url) = explode("\t", $result, 2);
$remote = new Remote();
$remotes[] = $remote->setName($name)->setUrl($url);
}
}
return $remotes;
}
}
@@ -0,0 +1,209 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Command;
use GuzzleHttp\Client;
use PhpCoveralls\Bundle\CoverallsBundle\Api\Jobs;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configurator;
use PhpCoveralls\Bundle\CoverallsBundle\Repository\JobsRepository;
use PhpCoveralls\Component\File\Path;
use PhpCoveralls\Component\Log\ConsoleLogger;
use Psr\Log\NullLogger;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Coveralls Jobs API v1 command.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class CoverallsJobsCommand extends Command
{
/**
* Skip SSL verification while sending a report if true.
*
* @var bool
*/
protected $allowInsecure;
/**
* Path to project root directory.
*
* @var string
*/
protected $rootDir;
/**
* Logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
// accessor
/**
* Set root directory.
*
* @param string $rootDir path to project root directory
*/
public function setRootDir($rootDir)
{
$this->rootDir = $rootDir;
}
// internal method
/**
* @see \Symfony\Component\Console\Command\Command::configure()
*/
protected function configure()
{
$this
->setName('coveralls:v1:jobs')
->setDescription('Coveralls Jobs API v1')
->addOption(
'config',
'-c',
InputOption::VALUE_OPTIONAL,
'.coveralls.yml path',
'.coveralls.yml'
)
->addOption(
'dry-run',
null,
InputOption::VALUE_NONE,
'Do not send json_file to Jobs API'
)
->addOption(
'exclude-no-stmt',
null,
InputOption::VALUE_NONE,
'Exclude source files that have no executable statements'
)
->addOption(
'env',
'-e',
InputOption::VALUE_OPTIONAL,
'Runtime environment name: test, dev, prod',
'prod'
)
->addOption(
'coverage_clover',
'-x',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Coverage clover xml files (allowing multiple values).',
[]
)
->addOption(
'json_path',
'-o',
InputOption::VALUE_REQUIRED,
'Coveralls output json file',
[]
)
->addOption(
'entry_point',
null,
InputOption::VALUE_REQUIRED,
'Coveralls entrypoint',
'https://coveralls.io'
)
->addOption(
'root_dir',
'-r',
InputOption::VALUE_OPTIONAL,
'Root directory of the project.',
'.'
)
->addOption(
'insecure',
'-k',
InputOption::VALUE_NONE,
'Skip SSL certificate check.'
)
;
}
/**
* @see \Symfony\Component\Console\Command\Command::execute()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$stopwatch = new Stopwatch();
$stopwatch->start(__CLASS__);
$file = new Path();
if ($input->getOption('root_dir') !== '.') {
$this->rootDir = $file->toAbsolutePath(
$input->getOption('root_dir'),
$this->rootDir
);
}
$config = $this->loadConfiguration($input, $this->rootDir);
$this->logger = $config->isVerbose() && !$config->isTestEnv() ? new ConsoleLogger($output) : new NullLogger();
$this->allowInsecure = (bool) $input->getOption('insecure');
$executionStatus = $this->executeApi($config);
$event = $stopwatch->stop(__CLASS__);
$time = number_format($event->getDuration() / 1000, 3); // sec
$mem = number_format($event->getMemory() / (1024 * 1024), 2); // MB
$this->logger->info(sprintf('elapsed time: <info>%s</info> sec memory: <info>%s</info> MB', $time, $mem));
return $executionStatus ? 0 : 1;
}
// for Jobs API
/**
* Load configuration.
*
* @param InputInterface $input input arguments
* @param string $rootDir path to project root directory
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration
*/
protected function loadConfiguration(InputInterface $input, $rootDir)
{
$coverallsYmlPath = $input->getOption('config');
$ymlPath = $this->rootDir . \DIRECTORY_SEPARATOR . $coverallsYmlPath;
$configurator = new Configurator();
return $configurator
->load($ymlPath, $rootDir, $input)
->setDryRun($input->getOption('dry-run'))
->setExcludeNoStatementsUnlessFalse($input->getOption('exclude-no-stmt'))
->setVerbose($input->getOption('verbose'))
->setEnv($input->getOption('env'))
;
}
/**
* Execute Jobs API.
*
* @param Configuration $config configuration
*
* @return bool
*/
protected function executeApi(Configuration $config)
{
$params = [];
if ($this->allowInsecure) {
$params['verify'] = false;
}
$client = new Client($params);
$api = new Jobs($config, $client);
$repository = new JobsRepository($api, $config);
$repository->setLogger($this->logger);
return $repository->persist();
}
}
@@ -0,0 +1,397 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Config;
/**
* Coveralls API configuration.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class Configuration
{
/**
* Entry point which is used for api calls.
*
* @var string
*/
protected $entryPoint;
// same as ruby lib
/**
* repo_token.
*
* @var string
*/
protected $repoToken;
/**
* service_name.
*
* @var string
*/
protected $serviceName;
// only for php lib
/**
* Absolute path to repository root directory.
*
* @var string
*/
protected $rootDir;
/**
* Absolute paths to clover.xml.
*
* @var array
*/
protected $cloverXmlPaths = [];
/**
* Absolute path to output json_file.
*
* @var string
*/
protected $jsonPath;
// from command option
/**
* Whether to send json_file to jobs API.
*
* @var bool
*/
protected $dryRun = true;
/**
* Whether to exclude source files that have no executable statements.
*
* @var bool
*/
protected $excludeNoStatements = false;
/**
* Whether to show log.
*
* @var bool
*/
protected $verbose = false;
/**
* Runtime environment name.
*
* @var string
*/
protected $env = 'prod';
// accessor
/**
* Set api entry point.
*
* @param string $entryPoint
*
* @return $this
*/
public function setEntryPoint($entryPoint)
{
$this->entryPoint = rtrim($entryPoint, '/');
return $this;
}
/**
* Return api entry point.
*
* @return string
*/
public function getEntryPoint()
{
return $this->entryPoint;
}
/**
* Set repository token.
*
* @param string $repoToken
*
* @return $this
*/
public function setRepoToken($repoToken)
{
$this->repoToken = $repoToken;
return $this;
}
/**
* Return whether repository token is configured.
*
* @return bool
*/
public function hasRepoToken()
{
return $this->repoToken !== null;
}
/**
* Return repository token.
*
* @return null|string
*/
public function getRepoToken()
{
return $this->repoToken;
}
/**
* Set service name.
*
* @param string $serviceName
*
* @return $this
*/
public function setServiceName($serviceName)
{
$this->serviceName = $serviceName;
return $this;
}
/**
* Return whether the service name is configured.
*
* @return bool
*/
public function hasServiceName()
{
return $this->serviceName !== null;
}
/**
* Return service name.
*
* @return null|string
*/
public function getServiceName()
{
return $this->serviceName;
}
public function setRootDir($rootDir)
{
$this->rootDir = $rootDir;
return $this;
}
public function getRootDir()
{
return $this->rootDir;
}
/**
* Set absolute paths to clover.xml.
*
* @param string[] $cloverXmlPaths
*
* @return $this
*/
public function setCloverXmlPaths(array $cloverXmlPaths)
{
$this->cloverXmlPaths = $cloverXmlPaths;
return $this;
}
/**
* Add absolute path to clover.xml.
*
* @param string $cloverXmlPath
*
* @return $this
*/
public function addCloverXmlPath($cloverXmlPath)
{
$this->cloverXmlPaths[] = $cloverXmlPath;
return $this;
}
/**
* Return absolute path to clover.xml.
*
* @return string[]
*/
public function getCloverXmlPaths()
{
return $this->cloverXmlPaths;
}
/**
* Set absolute path to output json_file.
*
* @param string $jsonPath
*
* @return $this
*/
public function setJsonPath($jsonPath)
{
$this->jsonPath = $jsonPath;
return $this;
}
/**
* Return absolute path to output json_file.
*
* @return string
*/
public function getJsonPath()
{
return $this->jsonPath;
}
/**
* Set whether to send json_file to jobs API.
*
* @param bool $dryRun
*
* @return $this
*/
public function setDryRun($dryRun)
{
$this->dryRun = $dryRun;
return $this;
}
/**
* Return whether to send json_file to jobs API.
*
* @return bool
*/
public function isDryRun()
{
return $this->dryRun;
}
/**
* Set whether to exclude source files that have no executable statements.
*
* @param bool $excludeNoStatements
*
* @return $this
*/
public function setExcludeNoStatements($excludeNoStatements)
{
$this->excludeNoStatements = $excludeNoStatements;
return $this;
}
/**
* Set whether to exclude source files that have no executable statements unless false.
*
* @param bool $excludeNoStatements
*
* @return $this
*/
public function setExcludeNoStatementsUnlessFalse($excludeNoStatements)
{
if ($excludeNoStatements) {
$this->excludeNoStatements = true;
}
return $this;
}
/**
* Return whether to exclude source files that have no executable statements.
*
* @return bool
*/
public function isExcludeNoStatements()
{
return $this->excludeNoStatements;
}
/**
* Set whether to show log.
*
* @param bool $verbose
*
* @return $this
*/
public function setVerbose($verbose)
{
$this->verbose = $verbose;
return $this;
}
/**
* Return whether to show log.
*
* @return bool
*/
public function isVerbose()
{
return $this->verbose;
}
/**
* Set runtime environment name.
*
* @param string $env runtime environment name
*
* @return $this
*/
public function setEnv($env)
{
$this->env = $env;
return $this;
}
/**
* Return runtime environment name.
*
* @return string
*/
public function getEnv()
{
return $this->env;
}
/**
* Return whether the runtime environment is test.
*
* @return bool
*/
public function isTestEnv()
{
return $this->env === 'test';
}
/**
* Return whether the runtime environment is dev.
*
* @return bool
*/
public function isDevEnv()
{
return $this->env === 'dev';
}
/**
* Return whether the runtime environment is prod.
*
* @return bool
*/
public function isProdEnv()
{
return $this->env === 'prod';
}
}
@@ -0,0 +1,255 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Config;
use PhpCoveralls\Component\File\Path;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Yaml\Parser;
/**
* Coveralls API configurator.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class Configurator
{
// API
/**
* Load configuration.
*
* @param string $coverallsYmlPath Path to .coveralls.yml.
* @param string $rootDir path to project root directory
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration
*
* @throws \Symfony\Component\Yaml\Exception\ParseException If the YAML is not valid
*/
public function load($coverallsYmlPath, $rootDir, InputInterface $input = null)
{
$yml = $this->parse($coverallsYmlPath);
$options = $this->process($yml);
return $this->createConfiguration($options, $rootDir, $input);
}
// Internal method
/**
* Parse .coveralls.yml.
*
* @param string $coverallsYmlPath Path to .coveralls.yml.
*
* @return array
*
* @throws \Symfony\Component\Yaml\Exception\ParseException If the YAML is not valid
*/
protected function parse($coverallsYmlPath)
{
$file = new Path();
$path = realpath($coverallsYmlPath);
if ($file->isRealFileReadable($path)) {
$parser = new Parser();
$yml = $parser->parse(file_get_contents($path));
return empty($yml) ? [] : $yml;
}
return [];
}
/**
* Process parsed configuration according to the configuration definition.
*
* @param array $yml parsed configuration
*
* @return array
*/
protected function process(array $yml)
{
$processor = new Processor();
$configuration = new CoverallsConfiguration();
return $processor->processConfiguration($configuration, ['coveralls' => $yml]);
}
/**
* Create coveralls configuration.
*
* @param array $options processed configuration
* @param string $rootDir path to project root directory
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration
*/
protected function createConfiguration(array $options, $rootDir, InputInterface $input = null)
{
$configuration = new Configuration();
$file = new Path();
$repoToken = $options['repo_token'];
$repoSecretToken = $options['repo_secret_token'];
$coverage_clover = $this->getPotentiallyOverriddenOptionValue('coverage_clover', $options, $input);
$json_path = $this->getPotentiallyOverriddenOptionValue('json_path', $options, $input);
$entry_point = $this->getPotentiallyOverriddenOptionValue('entry_point', $options, $input);
return $configuration
->setEntrypoint($entry_point)
->setRepoToken($repoToken !== null ? $repoToken : $repoSecretToken)
->setServiceName($options['service_name'])
->setRootDir($rootDir)
->setCloverXmlPaths($this->ensureCloverXmlPaths($coverage_clover, $rootDir, $file))
->setJsonPath($this->ensureJsonPath($json_path, $rootDir, $file))
->setExcludeNoStatements($options['exclude_no_stmt'])
;
}
/**
* Ensure coverage_clover is valid.
*
* @param string $option coverage_clover option
* @param string $rootDir path to project root directory
* @param Path $file path object
*
* @return string[] valid Absolute paths of coverage_clover
*
* @throws \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
protected function ensureCloverXmlPaths($option, $rootDir, Path $file)
{
if (\is_array($option)) {
return $this->getGlobPathsFromArrayOption($option, $rootDir, $file);
}
return $this->getGlobPathsFromStringOption($option, $rootDir, $file);
}
/**
* Return absolute paths from glob path.
*
* @param string $path absolute path
*
* @return string[] absolute paths
*
* @throws \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
protected function getGlobPaths($path)
{
$paths = [];
$iterator = new \GlobIterator($path);
foreach ($iterator as $fileInfo) {
/* @var $fileInfo \SplFileInfo */
$paths[] = $fileInfo->getPathname();
}
// validate
if (\count($paths) === 0) {
throw new InvalidConfigurationException("coverage_clover XML file is not readable: {$path}");
}
return $paths;
}
/**
* Return absolute paths from string option value.
*
* @param string $option coverage_clover option value
* @param string $rootDir path to project root directory
* @param Path $file path object
*
* @return string[] absolute paths
*
* @throws \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
protected function getGlobPathsFromStringOption($option, $rootDir, Path $file)
{
if (!\is_string($option)) {
throw new InvalidConfigurationException('coverage_clover XML file option must be a string');
}
// normalize
$path = $file->toAbsolutePath($option, $rootDir);
return $this->getGlobPaths($path);
}
/**
* Return absolute paths from array option values.
*
* @param array $options coverage_clover option values
* @param string $rootDir path to project root directory
* @param Path $file path object
*
* @return string[] absolute paths
*
* @throws \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
protected function getGlobPathsFromArrayOption(array $options, $rootDir, Path $file)
{
$paths = [];
foreach ($options as $option) {
$paths = array_merge($paths, $this->getGlobPathsFromStringOption($option, $rootDir, $file));
}
return $paths;
}
/**
* Ensure json_path is valid.
*
* @param string $option json_path option
* @param string $rootDir path to project root directory
* @param Path $file path object
*
* @return string valid json_path
*
* @throws \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
*/
protected function ensureJsonPath($option, $rootDir, Path $file)
{
// normalize
$realpath = $file->getRealWritingFilePath($option, $rootDir);
// validate file
$realFilePath = $file->getRealPath($realpath, $rootDir);
if ($realFilePath !== false && !$file->isRealFileWritable($realFilePath)) {
throw new InvalidConfigurationException("json_path is not writable: {$realFilePath}");
}
// validate parent dir
$realDir = $file->getRealDir($realpath, $rootDir);
if (!$file->isRealDirWritable($realDir)) {
throw new InvalidConfigurationException("json_path is not writable: {$realFilePath}");
}
return $realpath;
}
/**
* Get option from YAML config, potentially overridden via input params.
*
* @param string $optionName option name
* @param array $options processed configuration
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration
*/
private function getPotentiallyOverriddenOptionValue($optionName, array $options, InputInterface $input = null)
{
$value = $options[$optionName];
if ($input !== null && $input->hasOption($optionName)) {
$inputOverride = $input->getOption($optionName);
if (!empty($inputOverride)) {
$value = $inputOverride;
}
}
return $value;
}
}
@@ -0,0 +1,69 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Config;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* Definition of .coveralls.yml configuration.
*
* # same as ruby
* repo_token: your-token
* repo_secret_token: your-token
* service_name: travis-pro
*
* coverage_clover: build/logs/clover.xml
* json_path: build/logs/coveralls-upload.json
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class CoverallsConfiguration implements ConfigurationInterface
{
// ConfigurationInterface
/**
* @see \Symfony\Component\Config\Definition\ConfigurationInterface::getConfigTreeBuilder()
*/
public function getConfigTreeBuilder()
{
// define configuration
if (method_exists(TreeBuilder::class, 'getRootNode')) {
$treeBuilder = new TreeBuilder('coveralls');
$rootNode = $treeBuilder->getRootNode();
} else {
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('coveralls');
}
$rootNode
->children()
// same as ruby lib
->scalarNode('entry_point')
->defaultValue('https://coveralls.io')
->end()
->scalarNode('repo_token')
->defaultNull()
->end()
->scalarNode('repo_secret_token')
->defaultNull()
->end()
->scalarNode('service_name')
->defaultNull()
->end()
->variableNode('coverage_clover')
->defaultValue('build/logs/clover.xml')
->end()
->scalarNode('json_path')
->defaultValue('build/logs/coveralls-upload.json')
->end()
->booleanNode('exclude_no_stmt')
->defaultFalse()
->end()
->end()
;
return $treeBuilder;
}
}
@@ -0,0 +1,89 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Console;
use PhpCoveralls\Bundle\CoverallsBundle\Command\CoverallsJobsCommand;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Input\InputInterface;
/**
* Coveralls API application.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class Application extends BaseApplication
{
/**
* Path to project root directory.
*
* @var string
*/
private $rootDir;
/**
* Constructor.
*
* @param string $rootDir path to project root directory
* @param string $name The name of the application
* @param string $version The version of the application
*/
public function __construct($rootDir, $name = 'UNKNOWN', $version = 'UNKNOWN')
{
$this->rootDir = $rootDir;
parent::__construct($name, $version);
}
// accessor
/**
* @see \Symfony\Component\Console\Application::getDefinition()
*/
public function getDefinition()
{
$inputDefinition = parent::getDefinition();
// clear out the normal first argument, which is the command name
$inputDefinition->setArguments();
return $inputDefinition;
}
// internal method
/**
* @see \Symfony\Component\Console\Application::getCommandName()
*/
protected function getCommandName(InputInterface $input)
{
unset($input);
return 'coveralls:v1:jobs';
}
/**
* @see \Symfony\Component\Console\Application::getDefaultCommands()
*/
protected function getDefaultCommands()
{
// Keep the core default commands to have the HelpCommand
// which is used when using the --help option
$defaultCommands = parent::getDefaultCommands();
$defaultCommands[] = $this->createCoverallsJobsCommand();
return $defaultCommands;
}
/**
* Create CoverallsJobsCommand.
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Console\CoverallsJobsCommand
*/
protected function createCoverallsJobsCommand()
{
$command = new CoverallsJobsCommand();
$command->setRootDir($this->rootDir);
return $command;
}
}
@@ -0,0 +1,56 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Console;
use PhpCoveralls\Bundle\CoverallsBundle\Command\CoverallsJobsCommand;
use PhpCoveralls\Bundle\CoverallsBundle\Console\Application as CoverallsApplication;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\SingleCommandApplication;
/**
* In symfony/console 3.1 and lower provides only one way to realize single command application.
* It is possible by extending the {@see Application}
* Since symfony/console 6 applied strict typing which makes impossible to extending.
* Since symfony/console 3.1 added public method setDefaultCommand()
* Since symfony/console 3.2 method setDefaultCommand() accepts a boolean as second parameter, so we can build
* single command application without extending.
* But we can't rely on condition is method setDefaultCommand exists to apply new way without extending
* Since symfony/console 5.1 introduced new class {@see SingleCommandApplication}. But we can't use that without
* huge code duplication from the {@see CoverallsJobsCommand}.
* Nevertheless, when SingleCommandApplication exists we can be sure - setDefaultCommand can be applied.
*
* @see https://symfony.com/doc/3.1/components/console/single_command_tool.html
* @see https://symfony.com/doc/3.2/components/console/single_command_tool.html
* @see https://symfony.com/doc/5.1/components/console/single_command_tool.html
*
* @author Viktor Linkin <adrenalinkin@gmail.com>
*/
class ApplicationFactory
{
const APP_NAME = 'PHP client library for Coveralls API';
const APP_VERSION = '@package_version@';
/**
* @param string $rootDir
*
* @return Application
*/
public static function create($rootDir)
{
$command = new CoverallsJobsCommand();
$command->setRootDir($rootDir);
if (class_exists(SingleCommandApplication::class) === false) {
$application = new CoverallsApplication(self::APP_NAME, self::APP_VERSION);
$application->add($command);
return $application;
}
$application = new Application(self::APP_NAME, self::APP_VERSION);
$application->add($command);
$application->setDefaultCommand($command->getName(), true);
return $application;
}
}
@@ -0,0 +1,14 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Coveralls bundle.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class CoverallsBundle extends Bundle
{
}
@@ -0,0 +1,18 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Entity;
/**
* Array convertable entity.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
interface ArrayConvertable
{
/**
* Convert to an array.
*
* @return array
*/
public function toArray();
}
@@ -0,0 +1,35 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Entity;
/**
* Data for Coveralls API.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
abstract class Coveralls implements ArrayConvertable
{
/**
* String expression (convert to json).
*
* @return string
*/
public function __toString()
{
$result = json_encode($this->toArray());
if (json_last_error() !== JSON_ERROR_NONE) {
$this->onJsonError();
}
return (string) $result;
}
protected function onJsonError()
{
throw new \UnexpectedValueException(sprintf(
'Can not encode to JSON, error: "%s". If you have non-UTF8 chars, consider migration to UTF8.',
json_last_error_msg()
));
}
}
@@ -0,0 +1,131 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Entity\Exception;
/**
* Requirements of json_file are not satisfied.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class RequirementsNotSatisfiedException extends \RuntimeException
{
/**
* Error message.
*
* @var string
*/
protected $message = 'Requirements are not satisfied.';
/**
* Read environment variables.
*
* @var array
*/
protected $readEnv;
/**
* Array of secret env vars.
*
* @var string[]
*/
private static $secretEnvVars = [
'COVERALLS_REPO_TOKEN',
];
/**
* Return help message.
*
* @return string
*/
public function getHelpMessage()
{
$message = $this->message . "\n";
if (\is_array($this->readEnv)) {
foreach ($this->readEnv as $envVarName => $value) {
$message .= $this->format($envVarName, $value);
}
}
$message .= <<< 'EOL'
Set environment variables properly like the following.
For Travis users:
- TRAVIS
- TRAVIS_BUILD_NUMBER
- TRAVIS_JOB_ID
For CircleCI users:
- CIRCLECI
- CIRCLE_WORKFLOW_ID
- COVERALLS_REPO_TOKEN
For Jenkins users:
- JENKINS_URL
- BUILD_NUMBER
- COVERALLS_REPO_TOKEN
For AppVeyor users:
- APPVEYOR
- APPVEYOR_BUILD_NUMBER
For Github Actions users:
- GITHUB_REF
- GITHUB_ACTIONS
- GITHUB_RUN_ID
- GITHUB_EVENT_NAME
- COVERALLS_REPO_TOKEN
From local environment:
- COVERALLS_RUN_LOCALLY
- COVERALLS_REPO_TOKEN
EOL;
return $message;
}
/**
* Set read environment variables.
*
* @param array $readEnv read environment variables
*/
public function setReadEnv(array $readEnv)
{
$this->readEnv = $readEnv;
}
/**
* Return read environment variables.
*
* @return array
*/
public function getReadEnv()
{
return $this->readEnv;
}
/**
* Format a pair of the envVarName and the value.
*
* @param string $key the env var name
* @param string $value the value of the env var
*
* @return string
*/
protected function format($key, $value)
{
if (\in_array($key, self::$secretEnvVars, true)
&& \is_string($value)
&& $value !== '') {
$value = '********(HIDDEN)';
}
return sprintf(" - %s=%s\n", $key, var_export($value, true));
}
}
@@ -0,0 +1,218 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Entity\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Coveralls;
/**
* Commit info.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class Commit extends Coveralls
{
/**
* Commit ID.
*
* @var null|string
*/
protected $id;
/**
* Author name.
*
* @var null|string
*/
protected $authorName;
/**
* Author email.
*
* @var null|string
*/
protected $authorEmail;
/**
* Committer name.
*
* @var null|string
*/
protected $committerName;
/**
* Committer email.
*
* @var null|string
*/
protected $committerEmail;
/**
* Commit message.
*
* @var null|string
*/
protected $message;
// API
/**
* @see \PhpCoveralls\Bundle\CoverallsBundle\Entity\ArrayConvertable::toArray()
*/
public function toArray()
{
return [
'id' => $this->id,
'author_name' => $this->authorName,
'author_email' => $this->authorEmail,
'committer_name' => $this->committerName,
'committer_email' => $this->committerEmail,
'message' => $this->message,
];
}
// accessor
/**
* Set commit ID.
*
* @param string $id
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Return commit ID.
*
* @return null|string
*/
public function getId()
{
return $this->id;
}
/**
* Set author name.
*
* @param string $authorName
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit
*/
public function setAuthorName($authorName)
{
$this->authorName = $authorName;
return $this;
}
/**
* Return author name.
*
* @return null|string
*/
public function getAuthorName()
{
return $this->authorName;
}
/**
* Set author email.
*
* @param string $authorEmail
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit
*/
public function setAuthorEmail($authorEmail)
{
$this->authorEmail = $authorEmail;
return $this;
}
/**
* Return author email.
*
* @return null|string
*/
public function getAuthorEmail()
{
return $this->authorEmail;
}
/**
* Set committer name.
*
* @param string $committerName
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit
*/
public function setCommitterName($committerName)
{
$this->committerName = $committerName;
return $this;
}
/**
* Return committer name.
*
* @return null|string
*/
public function getCommitterName()
{
return $this->committerName;
}
/**
* Set committer email.
*
* @param string $committerEmail
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit
*/
public function setCommitterEmail($committerEmail)
{
$this->committerEmail = $committerEmail;
return $this;
}
/**
* Return committer email.
*
* @return null|string
*/
public function getCommitterEmail()
{
return $this->committerEmail;
}
/**
* Set commit message.
*
* @param string $message
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit
*/
public function setMessage($message)
{
$this->message = $message;
return $this;
}
/**
* Return commit message.
*
* @return null|string
*/
public function getMessage()
{
return $this->message;
}
}
@@ -0,0 +1,118 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Entity\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Coveralls;
/**
* Data represents "git" of Coveralls API.
*
* "git": {
* "head": {
* "id": "b31f08d07ae564b08237e5a336e478b24ccc4a65",
* "author_name": "Nick Merwin",
* "author_email": "...",
* "committer_name": "Nick Merwin",
* "committer_email": "...",
* "message": "version bump"
* },
* "branch": "master",
* "remotes": [
* {
* "name": "origin",
* "url": "git@github.com:lemurheavy/coveralls-ruby.git"
* }
* ]
* }
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class Git extends Coveralls
{
/**
* Branch name.
*
* @var string
*/
protected $branch;
/**
* Head.
*
* @var Commit
*/
protected $head;
/**
* Remote.
*
* @var Remote[]
*/
protected $remotes;
/**
* Constructor.
*
* @param string $branch branch name
* @param Commit $head hEAD commit
* @param array $remotes remote repositories
*/
public function __construct($branch, Commit $head, array $remotes)
{
$this->branch = $branch;
$this->head = $head;
$this->remotes = $remotes;
}
// API
/**
* @see \PhpCoveralls\Bundle\CoverallsBundle\Entity\ArrayConvertable::toArray()
*/
public function toArray()
{
$remotes = [];
foreach ($this->remotes as $remote) {
$remotes[] = $remote->toArray();
}
return [
'branch' => $this->branch,
'head' => $this->head->toArray(),
'remotes' => $remotes,
];
}
// accessor
/**
* Return branch name.
*
* @return string
*/
public function getBranch()
{
return $this->branch;
}
/**
* Return HEAD commit.
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit
*/
public function getHead()
{
return $this->head;
}
/**
* Return remote repositories.
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Remote[]
*/
public function getRemotes()
{
return $this->remotes;
}
}
@@ -0,0 +1,90 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Entity\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Coveralls;
/**
* Remote info.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class Remote extends Coveralls
{
/**
* Remote name.
*
* @var null|string
*/
protected $name;
/**
* Remote URL.
*
* @var null|string
*/
protected $url;
// API
/**
* @see \PhpCoveralls\Bundle\CoverallsBundle\Entity\ArrayConvertable::toArray()
*/
public function toArray()
{
return [
'name' => $this->name,
'url' => $this->url,
];
}
// accessor
/**
* Set remote name.
*
* @param string $name remote name
*
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Return remote name.
*
* @return null|string
*/
public function getName()
{
return $this->name;
}
/**
* Set remote URL.
*
* @param string $url remote URL
*
* @return $this
*/
public function setUrl($url)
{
$this->url = $url;
return $this;
}
/**
* Return remote URL.
*
* @return null|string
*/
public function getUrl()
{
return $this->url;
}
}
@@ -0,0 +1,725 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Entity;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Exception\RequirementsNotSatisfiedException;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Version;
/**
* Data represents "json_file" of Coveralls API.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
class JsonFile extends Coveralls
{
/**
* Service name.
*
* @var null|string
*/
protected $serviceName;
/**
* Service job id.
*
* @var null|string
*/
protected $serviceJobId;
/**
* Service number (not documented).
*
* @var string
*/
protected $serviceNumber;
/**
* Service event type (not documented).
*
* @var string
*/
protected $serviceEventType;
/**
* Build URL of the project (not documented).
*
* @var string
*/
protected $serviceBuildUrl;
/**
* Branch name (not documented).
*
* @var string
*/
protected $serviceBranch;
/**
* Pull request info (not documented).
*
* @var string
*/
protected $servicePullRequest;
/**
* Repository token.
*
* @var null|string
*/
protected $repoToken;
/**
* Source files.
*
* @var \PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile[]
*/
protected $sourceFiles = [];
/**
* Git data.
*
* @var null|Git
*/
protected $git;
/**
* A timestamp when the job ran. Must be parsable by Ruby.
*
* "2013-02-18 00:52:48 -0800"
*
* @var null|string
*/
protected $runAt;
/**
* Metrics.
*
* @var Metrics
*/
protected $metrics;
/**
* If this is set, the build will not be considered done until a webhook has
* been sent to https://coveralls.io/webhook?repo_token=….
*
* @var bool
*/
protected $parallel;
/**
* If this is set, the job being reported will be named in the view and have
* it’s own independent status reported to your VCS provider.
*
* @var string
*/
protected $flagName;
// API
/**
* @see \PhpCoveralls\Bundle\CoverallsBundle\Entity\ArrayConvertable::toArray()
*/
public function toArray()
{
$array = [];
$arrayMap = [
// json key => property name
'service_name' => 'serviceName',
'service_job_id' => 'serviceJobId',
'service_number' => 'serviceNumber',
'service_build_url' => 'serviceBuildUrl',
'service_branch' => 'serviceBranch',
'service_pull_request' => 'servicePullRequest',
'service_event_type' => 'serviceEventType',
'repo_token' => 'repoToken',
'parallel' => 'parallel',
'flag_name' => 'flagName',
'git' => 'git',
'run_at' => 'runAt',
'source_files' => 'sourceFiles',
];
foreach ($arrayMap as $jsonKey => $propName) {
if (isset($this->$propName)) {
$array[$jsonKey] = $this->toJsonProperty($this->$propName);
}
}
$array['environment'] = [
'packagist_version' => Version::VERSION,
];
return $array;
}
/**
* Fill environment variables.
*
* @param array $env $_SERVER environment
*
* @return $this
*
* @throws \RuntimeException
*/
public function fillJobs(array $env)
{
return $this
->fillStandardizedEnvVars($env)
->ensureJobs()
;
}
/**
* Exclude source files that have no executable statements.
*/
public function excludeNoStatementsFiles()
{
$this->sourceFiles = array_filter(
$this->sourceFiles,
function (SourceFile $sourceFile) {
return $sourceFile->getMetrics()->hasStatements();
}
);
}
/**
* Sort source files by path.
*/
public function sortSourceFiles()
{
ksort($this->sourceFiles);
}
/**
* Return line coverage.
*
* @return float
*/
public function reportLineCoverage()
{
$metrics = $this->getMetrics();
foreach ($this->sourceFiles as $sourceFile) {
/* @var $sourceFile \PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile */
$metrics->merge($sourceFile->getMetrics());
}
return $metrics->getLineCoverage();
}
// accessor
/**
* Return whether the json file has source file.
*
* @param string $path absolute path to source file
*
* @return bool
*/
public function hasSourceFile($path)
{
return isset($this->sourceFiles[$path]);
}
/**
* Return source file.
*
* @param string $path absolute path to source file
*
* @return null|\PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile
*/
public function getSourceFile($path)
{
if ($this->hasSourceFile($path)) {
return $this->sourceFiles[$path];
}
}
/**
* Add source file.
*/
public function addSourceFile(SourceFile $sourceFile)
{
$this->sourceFiles[$sourceFile->getPath()] = $sourceFile;
}
/**
* Return whether the json file has a source file.
*
* @return bool
*/
public function hasSourceFiles()
{
return \count($this->sourceFiles) > 0;
}
/**
* Return source files.
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile[]
*/
public function getSourceFiles()
{
return $this->sourceFiles;
}
/**
* Set service name.
*
* @param string $serviceName service name
*
* @return $this
*/
public function setServiceName($serviceName)
{
$this->serviceName = $serviceName;
return $this;
}
/**
* Return service name.
*
* @return null|string
*/
public function getServiceName()
{
return $this->serviceName;
}
/**
* Set repository token.
*
* @param string $repoToken repository token
*
* @return $this
*/
public function setRepoToken($repoToken)
{
$this->repoToken = $repoToken;
return $this;
}
/**
* Return repository token.
*
* @return null|string
*/
public function getRepoToken()
{
return $this->repoToken;
}
/**
* Set parallel.
*
* @param string $parallel parallel
*
* @return $this
*/
public function setParallel($parallel)
{
$this->parallel = $parallel;
return $this;
}
/**
* Return parallel.
*
* @return null|bool
*/
public function getParallel()
{
return $this->parallel;
}
/**
* Set flag name.
*
* @param string $flagName flag name
*
* @return $this
*/
public function setFlagName($flagName)
{
$this->flagName = $flagName;
return $this;
}
/**
* Return flag name.
*
* @return null|string
*/
public function getFlagName()
{
return $this->flagName;
}
/**
* Set service job id.
*
* @param string $serviceJobId service job id
*
* @return $this
*/
public function setServiceJobId($serviceJobId)
{
$this->serviceJobId = $serviceJobId;
return $this;
}
/**
* Return service job id.
*
* @return null|string
*/
public function getServiceJobId()
{
return $this->serviceJobId;
}
/**
* Return service number.
*
* @return string
*/
public function getServiceNumber()
{
return $this->serviceNumber;
}
/**
* Return service event type.
*
* @return string
*/
public function getServiceEventType()
{
return $this->serviceEventType;
}
/**
* Return build URL of the project.
*
* @return string
*/
public function getServiceBuildUrl()
{
return $this->serviceBuildUrl;
}
/**
* Return branch name.
*
* @return string
*/
public function getServiceBranch()
{
return $this->serviceBranch;
}
/**
* Return pull request info.
*
* @return string
*/
public function getServicePullRequest()
{
return $this->servicePullRequest;
}
/**
* Set git data.
*
* @param Git $git git data
*
* @return $this
*/
public function setGit(Git $git)
{
$this->git = $git;
return $this;
}
/**
* Return git data.
*
* @return null|Git
*/
public function getGit()
{
return $this->git;
}
/**
* Set timestamp when the job ran.
*
* @param string $runAt timestamp
*
* @return $this
*/
public function setRunAt($runAt)
{
$this->runAt = $runAt;
return $this;
}
/**
* Return timestamp when the job ran.
*
* @return null|string
*/
public function getRunAt()
{
return $this->runAt;
}
/**
* Return metrics.
*
* @return \PhpCoveralls\Bundle\CoverallsBundle\Entity\Metrics
*/
public function getMetrics()
{
if ($this->metrics === null) {
$this->metrics = new Metrics();
}
return $this->metrics;
}
// internal method
/**
* Convert to json property.
*
* @param mixed $prop
*/
protected function toJsonProperty($prop)
{
if ($prop instanceof Coveralls) {
return $prop->toArray();
}
if (\is_array($prop)) {
return $this->toJsonPropertyArray($prop);
}
return $prop;
}
/**
* Convert to array as json property.
*
* @return array
*/
protected function toJsonPropertyArray(array $propArray)
{
$array = [];
foreach ($propArray as $prop) {
$array[] = $this->toJsonProperty($prop);
}
return $array;
}
/**
* Fill standardized environment variables.
*
* "CI_NAME", "CI_BUILD_NUMBER" must be set.
*
* Env vars are:
*
* * CI_NAME
* * CI_BUILD_NUMBER
* * CI_BUILD_URL
* * CI_BRANCH
* * CI_PULL_REQUEST
*
* These vars are supported by Codeship.
*
* @param array $env $_SERVER environment
*
* @return $this
*/
protected function fillStandardizedEnvVars(array $env)
{
$map = [
// defined in Ruby lib
'serviceName' => 'CI_NAME',
'serviceNumber' => 'CI_BUILD_NUMBER',
'serviceBuildUrl' => 'CI_BUILD_URL',
'serviceBranch' => 'CI_BRANCH',
'servicePullRequest' => 'CI_PULL_REQUEST',
// extends by php-coveralls
'serviceJobId' => 'CI_JOB_ID',
'serviceEventType' => 'COVERALLS_EVENT_TYPE',
'repoToken' => 'COVERALLS_REPO_TOKEN',
'parallel' => 'COVERALLS_PARALLEL',
'flagName' => 'COVERALLS_FLAG_NAME',
];
foreach ($map as $propName => $envName) {
if (isset($env[$envName])) {
$this->$propName = $env[$envName];
}
}
return $this;
}
/**
* Ensure data consistency for jobs API.
*
* @return $this
*
* @throws \RuntimeException
*/
protected function ensureJobs()
{
if (!$this->hasSourceFiles()) {
throw new \RuntimeException('source_files must be set');
}
if ($this->requireServiceJobId()) {
return $this;
}
if ($this->requireServiceNumber()) {
return $this;
}
if ($this->requireServiceEventType()) {
return $this;
}
if ($this->requireRepoToken()) {
return $this;
}
if ($this->requireGithubActions()) {
return $this;
}
if ($this->isUnsupportedServiceJob()) {
return $this;
}
throw new RequirementsNotSatisfiedException();
}
/**
* Return whether the job requires "service_job_id" (for Travis CI).
*
* @return bool
*/
protected function requireServiceJobId()
{
return $this->serviceName !== null
&& $this->serviceNumber !== null
&& $this->serviceJobId !== null
&& $this->repoToken === null;
}
/**
* Return whether the job requires "service_number" (for CircleCI, Jenkins, Codeship or other CIs).
*
* @return bool
*/
protected function requireServiceNumber()
{
return $this->serviceName !== null
&& $this->serviceNumber !== null
&& $this->repoToken !== null;
}
/**
* Return whether the job requires "service_event_type" (for local environment).
*
* @return bool
*/
protected function requireServiceEventType()
{
return $this->serviceName !== null
&& $this->serviceEventType !== null
&& $this->repoToken !== null;
}
/**
* Return whether the job requires "repo_token" (for Travis PRO).
*
* @return bool
*/
protected function requireRepoToken()
{
return $this->serviceName === 'travis-pro'
&& $this->repoToken !== null;
}
/**
* Return whether the job requires "service_number", "service_job_id" and "repo_token" (for GithubActions).
*
* @return bool
*/
protected function requireGithubActions()
{
return $this->serviceName === 'github' && $this->serviceJobId !== null && $this->repoToken !== null;
}
/**
* Return whether the job is running on unsupported service.
*
* @return bool
*/
protected function isUnsupportedServiceJob()
{
return $this->serviceJobId === null
&& $this->serviceNumber === null
&& $this->serviceEventType === null
&& $this->repoToken !== null;
}
protected function onJsonError()
{
$array = $this->toArray();
if (!empty($array['git'])) {
$this->throwWhenInvalidJson($array['git'], '[git]');
}
$sourceFiles = empty($array['source_files']) ? [] : $array['source_files'];
foreach ($sourceFiles as $item) {
$this->throwWhenInvalidJson($item, '[source_files]: ' . $item['name']);
}
}
/**
* @param string $source
*/
private function throwWhenInvalidJson(array $item, $source)
{
json_encode($item);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \UnexpectedValueException(sprintf(
'Can not encode to JSON, error: "%s" in "%s".',
json_last_error_msg(),
$source
));
}
}
}
@@ -0,0 +1,137 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Entity;
/**
* Metrics.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class Metrics
{
/**
* Number of statements.
*
* @var int
*/
protected $statements = 0;
/**
* Number of covered statements.
*
* @var int
*/
protected $coveredStatements = 0;
/**
* Line coverage.
*
* @var float
*/
protected $lineCoverage;
/**
* Constructor.
*
* @param array $coverage coverage data
*/
public function __construct(array $coverage = [])
{
// statements
// not null
$statementsArray = array_filter(
$coverage,
function ($line) {
return $line !== null;
}
);
$this->statements = \count($statementsArray);
// covered statements
// gt 0
$coveredArray = array_filter(
$statementsArray,
function ($line) {
return $line > 0;
}
);
$this->coveredStatements = \count($coveredArray);
}
// API
/**
* Merge other metrics.
*/
public function merge(self $that)
{
$this->statements += $that->statements;
$this->coveredStatements += $that->coveredStatements;
$this->lineCoverage = null; // clear previous data
}
// accessor
/**
* Return whether the source file has executable statements.
*
* @return bool
*/
public function hasStatements()
{
return $this->statements !== 0;
}
/**
* Return number of statements.
*
* @return int
*/
public function getStatements()
{
return $this->statements;
}
/**
* Return number of covered statements.
*
* @return int
*/
public function getCoveredStatements()
{
return $this->coveredStatements;
}
/**
* Return line coverage.
*
* @return float
*/
public function getLineCoverage()
{
if ($this->lineCoverage === null) {
$this->lineCoverage = $this->calculateLineCoverage($this->statements, $this->coveredStatements);
}
return $this->lineCoverage;
}
// internal method
/**
* Calculate line coverage.
*
* @param int $statements number of statements
* @param int $coveredStatements number of covered statements
*
* @return float
*/
protected function calculateLineCoverage($statements, $coveredStatements)
{
if ($statements === 0) {
return 0;
}
return ($coveredStatements / $statements) * 100;
}
}
@@ -0,0 +1,174 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Entity;
/**
* Data represents "source_files" element of Coveralls' "json_file".
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class SourceFile extends Coveralls
{
/**
* 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 int
*/
protected $fileLines;
/**
* Metrics.
*
* @var Metrics
*/
protected $metrics;
/**
* 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 = trim(file_get_contents($path));
$lines = explode($eol, $this->source);
$this->fileLines = \count($lines);
$this->coverage = array_fill(0, $this->fileLines, null);
}
/**
* @see \PhpCoveralls\Bundle\CoverallsBundle\Entity\ArrayConvertable::toArray()
*/
public function toArray()
{
return [
'name' => $this->name,
'source' => $this->source,
'coverage' => $this->coverage,
];
}
// API
/**
* Add coverage.
*
* @param int $lineNum line number
* @param int $count number of covered
*/
public function addCoverage($lineNum, $count)
{
if (\array_key_exists($lineNum, $this->coverage)) {
$this->coverage[$lineNum] += $count;
}
}
/**
* Return line coverage.
*
* @return float
*/
public function reportLineCoverage()
{
return $this->getMetrics()->getLineCoverage();
}
// 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 int
*/
public function getFileLines()
{
return $this->fileLines;
}
/**
* Return metrics.
*
* @return Metrics
*/
public function getMetrics()
{
if ($this->metrics === null) {
$this->metrics = new Metrics($this->coverage);
}
return $this->metrics;
}
}
@@ -0,0 +1,290 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle\Repository;
use GuzzleHttp\Psr7\Response;
use PhpCoveralls\Bundle\CoverallsBundle\Api\Jobs;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
/**
* Jobs API client.
*
* Just wrap for logging.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class JobsRepository implements LoggerAwareInterface
{
/**
* Jobs API.
*
* @var \PhpCoveralls\Bundle\CoverallsBundle\Api\Jobs
*/
protected $api;
/**
* Configuration.
*
* @var \PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration
*/
protected $config;
/**
* Logger.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Constructor.
*
* @param Jobs $api aPI
* @param Configuration $config configuration
*/
public function __construct(Jobs $api, Configuration $config)
{
$this->api = $api;
$this->config = $config;
}
// API
/**
* Persist coverage data to Coveralls.
*
* @return bool
*/
public function persist()
{
try {
return $this
->collectCloverXml()
->collectGitInfo()
->collectEnvVars()
->dumpJsonFile()
->send()
;
} catch (\PhpCoveralls\Bundle\CoverallsBundle\Entity\Exception\RequirementsNotSatisfiedException $e) {
$this->logger->error(sprintf('%s', $e->getHelpMessage()));
return false;
} catch (\Exception $e) {
$this->logger->error(sprintf("%s\n\n%s", $e->getMessage(), $e->getTraceAsString()));
return false;
}
}
// LoggerAwareInterface
/**
* @see \Psr\Log\LoggerAwareInterface::setLogger()
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
// internal method
/**
* Collect clover XML into json_file.
*
* @return $this
*/
protected function collectCloverXml()
{
$this->logger->info('Load coverage clover log:');
foreach ($this->config->getCloverXmlPaths() as $path) {
$this->logger->info(sprintf(' - %s', $path));
}
$jsonFile = $this->api->collectCloverXml()->getJsonFile();
if ($jsonFile->hasSourceFiles()) {
$this->logCollectedSourceFiles($jsonFile);
}
return $this;
}
/**
* Collect git repository info into json_file.
*
* @return $this
*/
protected function collectGitInfo()
{
$this->logger->info('Collect git info');
$this->api->collectGitInfo();
return $this;
}
/**
* Collect environment variables.
*
* @return $this
*/
protected function collectEnvVars()
{
$this->logger->info('Read environment variables');
$this->api->collectEnvVars($_SERVER);
return $this;
}
/**
* Dump submitting json file.
*
* @return $this
*/
protected function dumpJsonFile()
{
$jsonPath = $this->config->getJsonPath();
$this->logger->info(sprintf('Dump submitting json file: %s', $jsonPath));
$this->api->dumpJsonFile();
$filesize = 0;
if (\is_string($jsonPath) && file_exists($jsonPath)) {
$filesize = number_format(filesize($jsonPath) / 1024, 2); // kB
}
$this->logger->info(sprintf('File size: <info>%s</info> kB', $filesize));
return $this;
}
/**
* Send json_file to Jobs API.
*
* @return bool
*/
protected function send()
{
$this->logger->info(sprintf('Submitting to %s', $this->config->getEntryPoint() . Jobs::URL));
try {
$response = $this->api->send();
$message = $response
? sprintf('Finish submitting. status: %s %s', $response->getStatusCode(), $response->getReasonPhrase())
: 'Finish dry run';
$this->logger->info($message);
if ($response instanceof Response) {
$this->logResponse($response);
}
return true;
} catch (\GuzzleHttp\Exception\ConnectException $e) {
// connection error
$message = sprintf("Connection error occurred. %s\n\n%s", $e->getMessage(), $e->getTraceAsString());
} catch (\GuzzleHttp\Exception\ClientException $e) {
// 422 Unprocessable Entity
$response = $e->getResponse();
$message = sprintf('Client error occurred. status: %s %s', $response->getStatusCode(), $response->getReasonPhrase());
} catch (\GuzzleHttp\Exception\ServerException $e) {
// 500 Internal Server Error
// 503 Service Unavailable
$response = $e->getResponse();
$message = sprintf('Server error occurred. status: %s %s', $response->getStatusCode(), $response->getReasonPhrase());
}
$this->logger->error($message);
if (isset($response)) {
$this->logResponse($response);
}
return false;
}
// logging
/**
* Colorize coverage.
*
* * green 90% - 100% <info>
* * yellow 80% - 90% <comment>
* * red 0% - 80% <fg=red>
*
* @param float $coverage coverage
* @param string $format format string to colorize
*
* @return string
*/
protected function colorizeCoverage($coverage, $format)
{
if ($coverage >= 90) {
return sprintf('<info>%s</info>', $format);
}
if ($coverage >= 80) {
return sprintf('<comment>%s</comment>', $format);
}
return sprintf('<fg=red>%s</fg=red>', $format);
}
/**
* Log collected source files.
*
* @param JsonFile $jsonFile json file
*/
protected function logCollectedSourceFiles(JsonFile $jsonFile)
{
$sourceFiles = $jsonFile->getSourceFiles();
$numFiles = \count($sourceFiles);
$this->logger->info(sprintf('Found <info>%s</info> source file%s:', number_format($numFiles), $numFiles > 1 ? 's' : ''));
foreach ($sourceFiles as $sourceFile) {
/** @var \PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile $sourceFile */
$coverage = $sourceFile->reportLineCoverage();
$template = ' - ' . $this->colorizeCoverage($coverage, '%6.2f%%') . ' %s';
$this->logger->info(sprintf($template, $coverage, $sourceFile->getName()));
}
$coverage = $jsonFile->reportLineCoverage();
$template = 'Coverage: ' . $this->colorizeCoverage($coverage, '%6.2f%% (%d/%d)');
$metrics = $jsonFile->getMetrics();
$this->logger->info(sprintf($template, $coverage, $metrics->getCoveredStatements(), $metrics->getStatements()));
}
/**
* Log response.
*
* @param Response $response aPI response
*/
protected function logResponse(Response $response)
{
$raw_body = (string) $response->getBody();
$body = json_decode($raw_body, true);
if ($body === null) {
// the response body is not in JSON format
$this->logger->error($raw_body);
} elseif (isset($body['error'])) {
if (isset($body['message'])) {
$this->logger->error($body['message']);
}
} else {
if (isset($body['message'])) {
$this->logger->info(sprintf('Accepted %s', $body['message']));
}
if (isset($body['url'])) {
$this->logger->info(sprintf('You can see the build on %s', $body['url']));
}
}
}
}
@@ -0,0 +1,18 @@
<?php
namespace PhpCoveralls\Bundle\CoverallsBundle;
/**
* CoverallsBundle version.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
final class Version
{
/**
* CoverallsBundle version.
*
* @var string
*/
const VERSION = '2.6.0';
}
@@ -0,0 +1,185 @@
<?php
namespace PhpCoveralls\Component\File;
/**
* Path utils.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class Path
{
/**
* Return whether the path is relative path.
*
* @param string $path path
*
* @return bool true if the path is relative path, false otherwise
*/
public function isRelativePath($path)
{
if ($path === '') {
return true;
}
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
return !preg_match('/^[a-z]+\:\\\\/i', $path);
}
return strpos($path, \DIRECTORY_SEPARATOR) !== 0;
}
/**
* Cat file path.
*
* @param string $path file path
* @param string $rootDir absolute path to project root directory
*
* @return false|string absolute path
*/
public function toAbsolutePath($path, $rootDir)
{
if (!\is_string($path)) {
return false;
}
if ($this->isRelativePath($path)) {
return $rootDir . \DIRECTORY_SEPARATOR . $path;
}
return $path;
}
/**
* Return real file path.
*
* @param string $path file path
* @param string $rootDir absolute path to project root directory
*
* @return false|string real path string if the path string is passed and real path exists, false otherwise
*/
public function getRealPath($path, $rootDir)
{
if (!\is_string($path)) {
return false;
}
if ($this->isRelativePath($path)) {
return realpath($rootDir . \DIRECTORY_SEPARATOR . $path);
}
return realpath($path);
}
/**
* Return real directory path.
*
* @param string $path path
* @param string $rootDir absolute path to project root directory
*
* @return false|string real directory path string if the path string is passed and real directory exists, false otherwise
*/
public function getRealDir($path, $rootDir)
{
if (!\is_string($path)) {
return false;
}
if ($this->isRelativePath($path)) {
return realpath($rootDir . \DIRECTORY_SEPARATOR . \dirname($path));
}
return realpath(\dirname($path));
}
/**
* Return real file path to write.
*
* @param string $path file path
* @param string $rootDir absolute path to project root directory
*
* @return false|string real file path string if the parent directory exists, false otherwise
*/
public function getRealWritingFilePath($path, $rootDir)
{
$realDir = $this->getRealDir($path, $rootDir);
if (!\is_string($realDir)) {
return false;
}
return $realDir . \DIRECTORY_SEPARATOR . basename($path);
}
/**
* Return whether the real path exists.
*
* @param bool|string $realpath real path
*
* @return bool true if the real path exists, false otherwise
*/
public function isRealPathExist($realpath)
{
return $realpath !== false && file_exists($realpath);
}
/**
* Return whether the real file path exists.
*
* @param bool|string $realpath real file path
*
* @return bool true if the real file path exists, false otherwise
*/
public function isRealFileExist($realpath)
{
return $this->isRealPathExist($realpath) && is_file($realpath);
}
/**
* Return whether the real file path is readable.
*
* @param bool|string $realpath real file path
*
* @return bool true if the real file path is readable, false otherwise
*/
public function isRealFileReadable($realpath)
{
return $this->isRealFileExist($realpath) && is_readable($realpath);
}
/**
* Return whether the real file path is writable.
*
* @param bool|string $realpath real file path
*
* @return bool true if the real file path is writable, false otherwise
*/
public function isRealFileWritable($realpath)
{
return $this->isRealFileExist($realpath) && is_writable($realpath);
}
/**
* Return whether the real directory exists.
*
* @param bool|string $realpath real directory path
*
* @return bool true if the real directory exists, false otherwise
*/
public function isRealDirExist($realpath)
{
return $this->isRealPathExist($realpath) && is_dir($realpath);
}
/**
* Return whether the real directory is writable.
*
* @param bool|string $realpath real directory path
*
* @return bool true if the real directory is writable, false otherwise
*/
public function isRealDirWritable($realpath)
{
return $this->isRealDirExist($realpath) && is_writable($realpath);
}
}
@@ -0,0 +1,42 @@
<?php
namespace PhpCoveralls\Component\Log;
use Psr\Log\AbstractLogger;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Console logger for php-coveralls command.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class ConsoleLogger extends AbstractLogger
{
/**
* Output.
*
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* Constructor.
*/
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
/**
* @see \Psr\Log\LoggerInterface::log()
*
* @param mixed $level
* @param mixed $message
*/
public function log($level, $message, array $context = [])
{
unset($level, $context);
$this->output->writeln($message);
}
}
@@ -0,0 +1,54 @@
<?php
namespace PhpCoveralls\Component\System\Git;
use PhpCoveralls\Component\System\SystemCommandExecutor;
use PhpCoveralls\Component\System\SystemCommandExecutorInterface;
/**
* Git command.
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*/
class GitCommand
{
/**
* @var SystemCommandExecutorInterface
*/
private $executor;
public function __construct(SystemCommandExecutorInterface $executor = null)
{
$this->executor = $executor ? $executor : new SystemCommandExecutor();
}
/**
* Return branch names.
*
* @return array
*/
public function getBranches()
{
return $this->executor->execute('git branch');
}
/**
* Return HEAD commit.
*
* @return array
*/
public function getHeadCommit()
{
return $this->executor->execute("git log -1 --pretty=format:'%H%n%aN%n%ae%n%cN%n%ce%n%s'");
}
/**
* Return remote repositories.
*
* @return array
*/
public function getRemotes()
{
return $this->executor->execute('git remote -v');
}
}
@@ -0,0 +1,32 @@
<?php
namespace PhpCoveralls\Component\System;
/**
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
final class SystemCommandExecutor implements SystemCommandExecutorInterface
{
/**
* Execute command.
*
* @param string $command
*
* @return array
*
* @throws \RuntimeException
*/
public function execute($command)
{
exec($command, $result, $returnValue);
if ($returnValue === 0) {
return $result;
}
throw new \RuntimeException(sprintf('Failed to execute command: %s', $command), $returnValue);
}
}
@@ -0,0 +1,22 @@
<?php
namespace PhpCoveralls\Component\System;
/**
* @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* @internal
*/
interface SystemCommandExecutorInterface
{
/**
* Execute command.
*
* @param string $command
*
* @return array
*
* @throws \RuntimeException
*/
public function execute($command);
}
@@ -0,0 +1,736 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Api;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
use PhpCoveralls\Bundle\CoverallsBundle\Api\Jobs;
use PhpCoveralls\Bundle\CoverallsBundle\Collector\CiEnvVarsCollector;
use PhpCoveralls\Bundle\CoverallsBundle\Collector\CloverXmlCoverageCollector;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Api\CoverallsApi
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Api\Jobs
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class JobsTest extends ProjectTestCase
{
// getJsonFile()
/**
* @test
*/
public function shouldNotHaveJsonFileOnConstruction()
{
$object = $this->createJobsNeverSendOnDryRun();
self::assertNull($object->getJsonFile());
}
// setJsonFile()
/**
* @test
*/
public function shouldSetJsonFile()
{
$jsonFile = $this->collectJsonFile();
$object = $this->createJobsNeverSendOnDryRun()->setJsonFile($jsonFile);
self::assertSame($jsonFile, $object->getJsonFile());
}
// getConfiguration()
/**
* @test
*/
public function shouldReturnConfiguration()
{
$config = $this->createConfiguration();
$object = new Jobs($config);
self::assertSame($config, $object->getConfiguration());
}
// getHttpClient()
/**
* @test
*/
public function shouldNotHaveHttpClientOnConstructionWithoutHttpClient()
{
$config = $this->createConfiguration();
$object = new Jobs($config);
self::assertNull($object->getHttpClient());
}
/**
* @test
*/
public function shouldHaveHttpClientOnConstructionWithHttpClient()
{
$config = $this->createConfiguration();
$client = $this->createAdapterMockNeverCalled();
$object = new Jobs($config, $client);
self::assertSame($client, $object->getHttpClient());
}
// setHttpClient()
/**
* @test
*/
public function shouldSetHttpClient()
{
$config = $this->createConfiguration();
$client = $this->createAdapterMockNeverCalled();
$object = new Jobs($config);
$object->setHttpClient($client);
self::assertSame($client, $object->getHttpClient());
}
// collectCloverXml()
/**
* @test
*/
public function shouldCollectCloverXml()
{
$this->makeProjectDir(null, $this->logsDir);
$xml = $this->getCloverXml();
file_put_contents($this->cloverXmlPath, $xml);
$config = $this->createConfiguration();
$object = new Jobs($config);
$same = $object->collectCloverXml();
// return $this
self::assertSame($same, $object);
return $object;
}
/**
* @test
*
* @depends shouldCollectCloverXml
*
* @return JsonFile
*/
public function shouldHaveJsonFileAfterCollectCloverXml(Jobs $object)
{
$jsonFile = $object->getJsonFile();
self::assertNotNull($jsonFile);
$sourceFiles = $jsonFile->getSourceFiles();
self::assertCount(4, $sourceFiles);
return $jsonFile;
}
/**
* @test
*
* @depends shouldHaveJsonFileAfterCollectCloverXml
*/
public function shouldNotHaveGitAfterCollectCloverXml(JsonFile $jsonFile)
{
$git = $jsonFile->getGit();
self::assertNull($git);
}
/**
* @test
*/
public function shouldCollectCloverXmlExcludingNoStatementsFiles()
{
$this->makeProjectDir(null, $this->logsDir);
$xml = $this->getCloverXml();
file_put_contents($this->cloverXmlPath, $xml);
$config = $this->createConfiguration()->setExcludeNoStatements(true);
$object = new Jobs($config);
$same = $object->collectCloverXml();
// return $this
self::assertSame($same, $object);
return $object;
}
/**
* @test
*
* @depends shouldCollectCloverXmlExcludingNoStatementsFiles
*
* @return JsonFile
*/
public function shouldHaveJsonFileAfterCollectCloverXmlExcludingNoStatementsFiles(Jobs $object)
{
$jsonFile = $object->getJsonFile();
self::assertNotNull($jsonFile);
$sourceFiles = $jsonFile->getSourceFiles();
self::assertCount(2, $sourceFiles);
return $jsonFile;
}
// collectGitInfo()
/**
* @test
*
* @depends shouldCollectCloverXml
*
* @return Jobs
*/
public function shouldCollectGitInfo(Jobs $object)
{
$same = $object->collectGitInfo();
// return $this
self::assertSame($same, $object);
return $object;
}
/**
* @test
*
* @depends shouldCollectGitInfo
*
* @return JsonFile
*/
public function shouldHaveJsonFileAfterCollectGitInfo(Jobs $object)
{
$jsonFile = $object->getJsonFile();
self::assertNotNull($jsonFile);
return $jsonFile;
}
/**
* @test
*
* @depends shouldHaveJsonFileAfterCollectGitInfo
*/
public function shouldHaveGitAfterCollectGitInfo(JsonFile $jsonFile)
{
$git = $jsonFile->getGit();
self::assertNotNull($git);
}
// send()
/**
* @test
*/
public function shouldSendTravisCiJob()
{
$this->makeProjectDir(null, $this->logsDir);
$serviceJobId = '1.1';
$server = [];
$server['TRAVIS'] = true;
$server['TRAVIS_BUILD_NUMBER'] = '654321';
$server['TRAVIS_JOB_ID'] = $serviceJobId;
$object = $this->createJobsWith();
$jsonFile = $this->collectJsonFile();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
}
/**
* @test
*/
public function shouldSendTravisProJob()
{
$this->makeProjectDir(null, $this->logsDir);
$serviceName = 'travis-pro';
$serviceJobId = '1.1';
$repoToken = 'your_token';
$server = [];
$server['TRAVIS'] = true;
$server['TRAVIS_BUILD_NUMBER'] = '8888';
$server['TRAVIS_JOB_ID'] = $serviceJobId;
$server['COVERALLS_REPO_TOKEN'] = $repoToken;
$object = $this->createJobsWith();
$object->getConfiguration()->setServiceName($serviceName);
$jsonFile = $this->collectJsonFile();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
self::assertSame($serviceName, $jsonFile->getServiceName());
self::assertSame($serviceJobId, $jsonFile->getServiceJobId());
self::assertSame($repoToken, $jsonFile->getRepoToken());
}
/**
* @test
*/
public function shouldSendCircleCiJob()
{
$this->makeProjectDir(null, $this->logsDir);
$serviceNumber = '123';
$repoToken = 'token';
$server = [];
$server['COVERALLS_REPO_TOKEN'] = $repoToken;
$server['CIRCLECI'] = 'true';
$server['CIRCLE_WORKFLOW_ID'] = $serviceNumber;
$object = $this->createJobsWith();
$jsonFile = $this->collectJsonFile();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
}
/**
* @test
*/
public function shouldSendJenkinsJob()
{
$this->makeProjectDir(null, $this->logsDir);
$serviceNumber = '123';
$repoToken = 'token';
$server = [];
$server['COVERALLS_REPO_TOKEN'] = $repoToken;
$server['JENKINS_URL'] = 'http://localhost:8080';
$server['BUILD_NUMBER'] = $serviceNumber;
$object = $this->createJobsWith();
$jsonFile = $this->collectJsonFile();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
}
/**
* @test
*/
public function shouldSendLocalJob()
{
$this->makeProjectDir(null, $this->logsDir);
$server = [];
$server['COVERALLS_RUN_LOCALLY'] = '1';
$object = $this->createJobsWith();
$object->getConfiguration()->setRepoToken('token');
$jsonFile = $this->collectJsonFile();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
}
/**
* @test
*/
public function shouldSendUnsupportedJob()
{
$this->makeProjectDir(null, $this->logsDir);
$server = [];
$server['COVERALLS_REPO_TOKEN'] = 'token';
$object = $this->createJobsWith();
$jsonFile = $this->collectJsonFile();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
}
/**
* @test
*/
public function shouldSendUnsupportedGitJob()
{
$this->makeProjectDir(null, $this->logsDir);
$server = [];
$server['COVERALLS_REPO_TOKEN'] = 'token';
$server['GIT_COMMIT'] = 'abc123';
$object = $this->createJobsWith();
$jsonFile = $this->collectJsonFile();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
}
/**
* @test
*/
public function shouldNotSendJobIfTestEnv()
{
$this->makeProjectDir(null, $this->logsDir);
$server = [];
$server['TRAVIS'] = true;
$server['TRAVIS_BUILD_NUMBER'] = '198765';
$server['TRAVIS_JOB_ID'] = '1.1';
$object = $this->createJobsNeverSendOnDryRun();
$object->getConfiguration()->setEnv('test');
$jsonFile = $this->collectJsonFile();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
}
/**
* @test
*/
public function throwRuntimeExceptionIfInvalidEnv()
{
$this->expectException(\RuntimeException::class);
$server = [];
$object = $this->createJobsNeverSend();
$jsonFile = $this->collectJsonFile();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
}
/**
* @test
*/
public function throwRuntimeExceptionIfNoSourceFiles()
{
$this->expectException(\RuntimeException::class);
$server = [];
$server['TRAVIS'] = true;
$server['TRAVIS_BUILD_NUMBER'] = '12345';
$server['TRAVIS_JOB_ID'] = '1.1';
$server['GIT_COMMIT'] = 'abc123';
$object = $this->createJobsNeverSend();
$jsonFile = $this->collectJsonFileWithoutSourceFiles();
$object
->setJsonFile($jsonFile)
->collectEnvVars($server)
->dumpJsonFile()
->send()
;
}
protected function legacySetUp()
{
$this->setUpDir(realpath(__DIR__ . '/../../..'));
}
protected function legacyTearDown()
{
$this->rmFile($this->jsonPath);
$this->rmFile($this->cloverXmlPath);
$this->rmDir($this->logsDir);
$this->rmDir($this->buildDir);
}
/**
* @return Jobs
*/
protected function createJobsWith()
{
$config = new Configuration();
$config
->setEntryPoint('https://coveralls.io')
->setJsonPath($this->jsonPath)
->setDryRun(false)
;
$client = $this->createAdapterMockWith($this->url, $this->filename);
return new Jobs($config, $client);
}
/**
* @return Jobs
*/
protected function createJobsNeverSend()
{
$config = new Configuration();
$config
->setEntryPoint('https://coveralls.io')
->setJsonPath($this->jsonPath)
->setDryRun(false)
;
$client = $this->createAdapterMockNeverCalled();
return new Jobs($config, $client);
}
/**
* @return Jobs
*/
protected function createJobsNeverSendOnDryRun()
{
$config = new Configuration();
$config
->setEntryPoint('https://coveralls.io')
->setJsonPath($this->jsonPath)
->setDryRun(true)
;
$client = $this->createAdapterMockNeverCalled();
return new Jobs($config, $client);
}
/**
* @return Client
*/
protected function createAdapterMockNeverCalled()
{
$client = $this->prophesize(Client::class);
$client
->send()
->shouldNotBeCalled()
;
return $client->reveal();
}
/**
* @param string $url
* @param string $filename
*
* @return Client
*/
protected function createAdapterMockWith($url, $filename)
{
$response = $this->prophesize(Psr7\Response::class);
$response->reveal();
$client = $this->prophesize(Client::class);
$client
->post($url, \Prophecy\Argument::that(function ($options) use ($filename) {
return !empty($options['multipart'][0]['name'])
&& !empty($options['multipart'][0]['contents'])
&& $filename === $options['multipart'][0]['name']
&& \is_string($options['multipart'][0]['contents']);
}))
->willReturn($response)
->shouldBeCalled()
;
return $client->reveal();
}
/**
* @return Configuration
*/
protected function createConfiguration()
{
$config = new Configuration();
$config->setRootDir('');
return $config->addCloverXmlPath($this->cloverXmlPath);
}
/**
* @return string
*/
protected function getCloverXml()
{
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1365848893">
<project timestamp="1365848893">
<file name="%s/test.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="0"/>
</file>
<file name="%s/TestInterface.php">
<class name="TestInterface" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="0" coveredstatements="0" elements="1" coveredelements="0"/>
</class>
<line num="5" type="method" name="hello" crap="1" count="0"/>
</file>
<file name="%s/AbstractClass.php">
<class name="AbstractClass" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="0" coveredstatements="0" elements="1" coveredelements="0"/>
</class>
<line num="5" type="method" name="hello" crap="1" count="0"/>
</file>
<file name="dummy.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="0"/>
</file>
<package name="Hoge">
<file name="%s/test2.php">
<class name="TestFile" namespace="Hoge">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="6" type="method" name="__construct" crap="1" count="0"/>
<line num="8" type="stmt" count="0"/>
</file>
</package>
</project>
</coverage>
XML;
return sprintf($xml, $this->srcDir, $this->srcDir, $this->srcDir, $this->srcDir);
}
/**
* @return \SimpleXMLElement
*/
protected function createCloverXml()
{
$xml = $this->getCloverXml();
return simplexml_load_string($xml);
}
/**
* @return string
*/
protected function getNoSourceCloverXml()
{
return <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1365848893">
<project timestamp="1365848893">
<file name="dummy.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="0"/>
</file>
</project>
</coverage>
XML;
}
/**
* @return \SimpleXMLElement
*/
protected function createNoSourceCloverXml()
{
$xml = $this->getNoSourceCloverXml();
return simplexml_load_string($xml);
}
/**
* @return JsonFile
*/
protected function collectJsonFile()
{
$xml = $this->createCloverXml();
$collector = new CloverXmlCoverageCollector();
return $collector->collect($xml, $this->srcDir);
}
/**
* @return JsonFile
*/
protected function collectJsonFileWithoutSourceFiles()
{
$xml = $this->createNoSourceCloverXml();
$collector = new CloverXmlCoverageCollector();
return $collector->collect($xml, $this->srcDir);
}
/**
* @param Configuration $config
*
* @return CiEnvVarsCollector
*/
protected function createCiEnvVarsCollector($config = null)
{
if ($config === null) {
$config = $this->createConfiguration();
}
return new CiEnvVarsCollector($config);
}
}
@@ -0,0 +1,497 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Collector;
use PhpCoveralls\Bundle\CoverallsBundle\Collector\CiEnvVarsCollector;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Collector\CiEnvVarsCollector
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class CiEnvVarsCollectorTest extends ProjectTestCase
{
// collect()
/**
* @test
*/
public function shouldCollectTravisCiEnvVars()
{
$serviceName = 'travis-ci';
$serviceJobId = '1.1';
$serviceBuildNumber = '123456';
$env = [];
$env['TRAVIS'] = true;
$env['TRAVIS_JOB_ID'] = $serviceJobId;
$env['TRAVIS_BUILD_NUMBER'] = $serviceBuildNumber;
$object = $this->createCiEnvVarsCollector();
$actual = $object->collect($env);
self::assertArrayHasKey('CI_NAME', $actual);
self::assertSame($serviceName, $actual['CI_NAME']);
self::assertArrayHasKey('CI_JOB_ID', $actual);
self::assertSame($serviceJobId, $actual['CI_JOB_ID']);
self::assertArrayHasKey('CI_BUILD_NUMBER', $actual);
self::assertSame($serviceBuildNumber, $actual['CI_BUILD_NUMBER']);
return $object;
}
/**
* @test
*/
public function shouldCollectTravisProEnvVars()
{
$serviceName = 'travis-pro';
$serviceJobId = '1.2';
$serviceBuildNumber = '12345';
$repoToken = 'your_token';
$env = [];
$env['TRAVIS'] = true;
$env['TRAVIS_JOB_ID'] = $serviceJobId;
$env['TRAVIS_BUILD_NUMBER'] = $serviceBuildNumber;
$env['COVERALLS_REPO_TOKEN'] = $repoToken;
$config = $this->createConfiguration();
$config->setServiceName($serviceName);
$object = $this->createCiEnvVarsCollector($config);
$actual = $object->collect($env);
self::assertArrayHasKey('CI_NAME', $actual);
self::assertSame($serviceName, $actual['CI_NAME']);
self::assertArrayHasKey('CI_JOB_ID', $actual);
self::assertSame($serviceJobId, $actual['CI_JOB_ID']);
self::assertArrayHasKey('CI_BUILD_NUMBER', $actual);
self::assertSame($serviceBuildNumber, $actual['CI_BUILD_NUMBER']);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $actual);
self::assertSame($repoToken, $actual['COVERALLS_REPO_TOKEN']);
return $object;
}
/**
* @test
*/
public function shouldCollectCircleCiEnvVars()
{
$serviceName = 'circleci';
$serviceNumber = '123';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = 'token';
$env['CIRCLECI'] = 'true';
$env['CIRCLE_WORKFLOW_ID'] = $serviceNumber;
$object = $this->createCiEnvVarsCollector();
$actual = $object->collect($env);
self::assertArrayHasKey('CI_NAME', $actual);
self::assertSame($serviceName, $actual['CI_NAME']);
self::assertArrayHasKey('CI_BUILD_NUMBER', $actual);
self::assertSame($serviceNumber, $actual['CI_BUILD_NUMBER']);
return $object;
}
/**
* @test
*/
public function shouldCollectJenkinsEnvVars()
{
$serviceName = 'jenkins';
$serviceNumber = '123';
$buildUrl = 'http://localhost:8080';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = 'token';
$env['JENKINS_URL'] = $buildUrl;
$env['BUILD_NUMBER'] = $serviceNumber;
$object = $this->createCiEnvVarsCollector();
$actual = $object->collect($env);
self::assertArrayHasKey('CI_NAME', $actual);
self::assertSame($serviceName, $actual['CI_NAME']);
self::assertArrayHasKey('CI_BUILD_NUMBER', $actual);
self::assertSame($serviceNumber, $actual['CI_BUILD_NUMBER']);
self::assertArrayHasKey('CI_BUILD_URL', $actual);
self::assertSame($buildUrl, $actual['CI_BUILD_URL']);
return $object;
}
/**
* @test
*/
public function shouldCollectGithubActionsEnvVars()
{
$serviceName = 'github';
$jobId = '275038505';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = 'token';
$env['GITHUB_ACTIONS'] = true;
$env['GITHUB_EVENT_NAME'] = 'push';
$env['GITHUB_REF'] = 'refs/heads/master';
$env['GITHUB_RUN_ID'] = '275038505';
$object = $this->createCiEnvVarsCollector();
$actual = $object->collect($env);
self::assertArrayHasKey('CI_NAME', $actual);
self::assertSame($serviceName, $actual['CI_NAME']);
self::assertArrayHasKey('CI_JOB_ID', $actual);
self::assertSame($jobId, $actual['CI_JOB_ID']);
self::assertArrayHasKey('CI_BRANCH', $actual);
self::assertSame('master', $actual['CI_BRANCH']);
return $object;
}
/**
* @test
*/
public function shouldCollectGithubActionsEnvVarsForPullRequest()
{
$serviceName = 'github';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = 'token';
$env['GITHUB_ACTIONS'] = true;
$env['GITHUB_EVENT_NAME'] = 'pull_request';
$env['GITHUB_REF'] = 'refs/pull/1/merge';
$env['GITHUB_RUN_ID'] = '275038505';
$object = $this->createCiEnvVarsCollector();
$actual = $object->collect($env);
self::assertArrayHasKey('CI_NAME', $actual);
self::assertSame($serviceName, $actual['CI_NAME']);
self::assertArrayHasKey('CI_JOB_ID', $actual);
self::assertSame('275038505', $actual['CI_JOB_ID']);
self::assertArrayHasKey('CI_BRANCH', $actual);
self::assertSame('refs/pull/1/merge', $actual['CI_BRANCH']);
return $object;
}
/**
* @test
*/
public function shouldCollectGithubActionsEnvVarsForTag()
{
$serviceName = 'github';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = 'token';
$env['GITHUB_ACTIONS'] = true;
$env['GITHUB_EVENT_NAME'] = 'push';
$env['GITHUB_REF'] = 'refs/tags/v123.456.789';
$env['GITHUB_RUN_ID'] = '275038505';
$object = $this->createCiEnvVarsCollector();
$actual = $object->collect($env);
self::assertArrayHasKey('CI_NAME', $actual);
self::assertSame($serviceName, $actual['CI_NAME']);
self::assertArrayHasKey('CI_JOB_ID', $actual);
self::assertSame('275038505', $actual['CI_JOB_ID']);
self::assertArrayHasKey('CI_BRANCH', $actual);
self::assertSame('v123.456.789', $actual['CI_BRANCH']);
return $object;
}
/**
* @test
*/
public function shouldCollectLocalEnvVars()
{
$serviceName = 'php-coveralls';
$serviceEventType = 'manual';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = 'token';
$env['COVERALLS_RUN_LOCALLY'] = '1';
$object = $this->createCiEnvVarsCollector();
$actual = $object->collect($env);
self::assertArrayHasKey('CI_NAME', $actual);
self::assertSame($serviceName, $actual['CI_NAME']);
self::assertArrayHasKey('COVERALLS_EVENT_TYPE', $actual);
self::assertSame($serviceEventType, $actual['COVERALLS_EVENT_TYPE']);
self::assertArrayHasKey('CI_JOB_ID', $actual);
self::assertNull($actual['CI_JOB_ID']);
return $object;
}
/**
* @test
*/
public function shouldCollectUnsupportedConfig()
{
$repoToken = 'token';
$env = [];
$config = $this->createConfiguration();
$config->setRepoToken($repoToken);
$object = $this->createCiEnvVarsCollector($config);
$actual = $object->collect($env);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $actual);
self::assertSame($repoToken, $actual['COVERALLS_REPO_TOKEN']);
return $object;
}
/**
* @test
*/
public function shouldCollectUnsupportedEnvVars()
{
$repoToken = 'token';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = $repoToken;
$object = $this->createCiEnvVarsCollector();
$actual = $object->collect($env);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $actual);
self::assertSame($repoToken, $actual['COVERALLS_REPO_TOKEN']);
return $object;
}
/**
* @test
*/
public function shouldCollectParallel()
{
$parallel = true;
$flagName = 'php-7.4';
$env = [];
$env['COVERALLS_PARALLEL'] = $parallel;
$env['COVERALLS_FLAG_NAME'] = $flagName;
$object = $this->createCiEnvVarsCollector();
$actual = $object->collect($env);
self::assertArrayHasKey('COVERALLS_PARALLEL', $actual);
self::assertSame($parallel, $actual['COVERALLS_PARALLEL']);
self::assertArrayHasKey('COVERALLS_FLAG_NAME', $actual);
self::assertSame($flagName, $actual['COVERALLS_FLAG_NAME']);
return $object;
}
// getReadEnv()
/**
* @test
*/
public function shouldNotHaveReadEnvOnConstruction()
{
$object = $this->createCiEnvVarsCollector();
self::assertNull($object->getReadEnv());
}
/**
* @test
*
* @depends shouldCollectTravisCiEnvVars
*/
public function shouldHaveReadEnvAfterCollectTravisCiEnvVars(CiEnvVarsCollector $object)
{
$readEnv = $object->getReadEnv();
self::assertCount(4, $readEnv);
self::assertArrayHasKey('TRAVIS', $readEnv);
self::assertArrayHasKey('TRAVIS_JOB_ID', $readEnv);
self::assertArrayHasKey('CI_NAME', $readEnv);
self::assertArrayHasKey('CI_BUILD_NUMBER', $readEnv);
}
/**
* @test
*
* @depends shouldCollectGithubActionsEnvVars
*/
public function shouldHaveReadEnvAfterCollectGithubActionsEnvVars(CiEnvVarsCollector $object)
{
$readEnv = $object->getReadEnv();
self::assertCount(6, $readEnv);
self::assertArrayHasKey('GITHUB_REF', $readEnv);
self::assertArrayHasKey('CI_NAME', $readEnv);
self::assertArrayHasKey('CI_JOB_ID', $readEnv);
self::assertArrayHasKey('GITHUB_ACTIONS', $readEnv);
self::assertArrayHasKey('CI_BRANCH', $readEnv);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $readEnv);
}
/**
* @test
*
* @depends shouldCollectTravisProEnvVars
*/
public function shouldHaveReadEnvAfterCollectTravisProEnvVars(CiEnvVarsCollector $object)
{
$readEnv = $object->getReadEnv();
self::assertCount(5, $readEnv);
self::assertArrayHasKey('TRAVIS', $readEnv);
self::assertArrayHasKey('TRAVIS_JOB_ID', $readEnv);
self::assertArrayHasKey('CI_NAME', $readEnv);
self::assertArrayHasKey('CI_BUILD_NUMBER', $readEnv);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $readEnv);
}
/**
* @test
*
* @depends shouldCollectCircleCiEnvVars
*/
public function shouldHaveReadEnvAfterCollectCircleCiEnvVars(CiEnvVarsCollector $object)
{
$readEnv = $object->getReadEnv();
self::assertCount(4, $readEnv);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $readEnv);
self::assertArrayHasKey('CIRCLECI', $readEnv);
self::assertArrayHasKey('CIRCLE_WORKFLOW_ID', $readEnv);
self::assertArrayHasKey('CI_NAME', $readEnv);
}
/**
* @test
*
* @depends shouldCollectJenkinsEnvVars
*/
public function shouldHaveReadEnvAfterCollectJenkinsEnvVars(CiEnvVarsCollector $object)
{
$readEnv = $object->getReadEnv();
self::assertCount(4, $readEnv);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $readEnv);
self::assertArrayHasKey('JENKINS_URL', $readEnv);
self::assertArrayHasKey('BUILD_NUMBER', $readEnv);
self::assertArrayHasKey('CI_NAME', $readEnv);
}
/**
* @test
*
* @depends shouldCollectLocalEnvVars
*/
public function shouldHaveReadEnvAfterCollectLocalEnvVars(CiEnvVarsCollector $object)
{
$readEnv = $object->getReadEnv();
self::assertCount(4, $readEnv);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $readEnv);
self::assertArrayHasKey('COVERALLS_RUN_LOCALLY', $readEnv);
self::assertArrayHasKey('COVERALLS_EVENT_TYPE', $readEnv);
self::assertArrayHasKey('CI_NAME', $readEnv);
}
/**
* @test
*
* @depends shouldCollectUnsupportedConfig
*/
public function shouldHaveReadEnvAfterCollectUnsupportedConfig(CiEnvVarsCollector $object)
{
$readEnv = $object->getReadEnv();
self::assertCount(1, $readEnv);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $readEnv);
}
/**
* @test
*
* @depends shouldCollectUnsupportedEnvVars
*/
public function shouldHaveReadEnvAfterCollectUnsupportedEnvVars(CiEnvVarsCollector $object)
{
$readEnv = $object->getReadEnv();
self::assertCount(1, $readEnv);
self::assertArrayHasKey('COVERALLS_REPO_TOKEN', $readEnv);
}
protected function legacySetUp()
{
$this->setUpDir(realpath(__DIR__ . '/../../..'));
}
/**
* @return Configuration
*/
protected function createConfiguration()
{
$config = new Configuration();
return $config
->addCloverXmlPath($this->cloverXmlPath)
;
}
/**
* @param null|Configuration $config
*
* @return CiEnvVarsCollector
*/
protected function createCiEnvVarsCollector($config = null)
{
if ($config === null) {
$config = $this->createConfiguration();
}
return new CiEnvVarsCollector($config);
}
}
@@ -0,0 +1,304 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Collector;
use PhpCoveralls\Bundle\CoverallsBundle\Collector\CloverXmlCoverageCollector;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Collector\CloverXmlCoverageCollector
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class CloverXmlCoverageCollectorTest extends ProjectTestCase
{
/**
* @var CloverXmlCoverageCollector
*/
private $object;
// getJsonFile()
/**
* @test
*/
public function shouldNotHaveJsonFileOnConstruction()
{
self::assertNull($this->object->getJsonFile());
}
// collect() under srcDir
/**
* @test
*/
public function shouldCollect()
{
$xml = $this->createCloverXml();
$jsonFile = $this->object->collect($xml, $this->srcDir);
self::assertSame($jsonFile, $this->object->getJsonFile());
$this->assertJsonFile($jsonFile, null, null, null, null, '2013-04-13 10:28:13 +0000');
return $jsonFile;
}
/**
* @test
*
* @depends shouldCollect
*
* @return JsonFile
*/
public function shouldCollectSourceFiles(JsonFile $jsonFile)
{
$sourceFiles = $jsonFile->getSourceFiles();
self::assertCount(3, $sourceFiles);
return $jsonFile;
}
/**
* @test
*
* @depends shouldCollectSourceFiles
*/
public function shouldCollectSourceFileTest1(JsonFile $jsonFile)
{
$sourceFiles = $jsonFile->getSourceFiles();
$name1 = 'test.php';
$path1 = $this->srcDir . \DIRECTORY_SEPARATOR . $name1;
self::assertArrayHasKey($path1, $sourceFiles);
$this->assertSourceFileTest1($sourceFiles[$path1]);
}
/**
* @test
*
* @depends shouldCollectSourceFiles
*/
public function shouldCollectSourceFileTest2(JsonFile $jsonFile)
{
$sourceFiles = $jsonFile->getSourceFiles();
$name2 = 'test2.php';
$path2 = $this->srcDir . \DIRECTORY_SEPARATOR . $name2;
self::assertArrayHasKey($path2, $sourceFiles);
$this->assertSourceFileTest2($sourceFiles[$path2]);
}
// collect() under /
/**
* @test
*/
public function shouldCollectUnderRootDir()
{
$xml = $this->createCloverXml();
$jsonFile = $this->object->collect($xml, \DIRECTORY_SEPARATOR);
self::assertSame($jsonFile, $this->object->getJsonFile());
$this->assertJsonFile($jsonFile, null, null, null, null, '2013-04-13 10:28:13 +0000');
return $jsonFile;
}
/**
* @test
*
* @depends shouldCollectUnderRootDir
*
* @return JsonFile
*/
public function shouldCollectSourceFilesUnderRootDir(JsonFile $jsonFile)
{
$sourceFiles = $jsonFile->getSourceFiles();
self::assertCount(3, $sourceFiles);
return $jsonFile;
}
/**
* @test
*
* @depends shouldCollectSourceFilesUnderRootDir
*/
public function shouldCollectSourceFileTest1UnderRootDir(JsonFile $jsonFile)
{
$sourceFiles = $jsonFile->getSourceFiles();
$name1 = 'test.php';
$path1 = $this->srcDir . \DIRECTORY_SEPARATOR . $name1;
self::assertArrayHasKey($path1, $sourceFiles);
$this->assertSourceFileTest1UnderRootDir($sourceFiles[$path1]);
}
/**
* @test
*
* @depends shouldCollectSourceFilesUnderRootDir
*/
public function shouldCollectSourceFileTest2UnderRootDir(JsonFile $jsonFile)
{
$sourceFiles = $jsonFile->getSourceFiles();
$name2 = 'test2.php';
$path2 = $this->srcDir . \DIRECTORY_SEPARATOR . $name2;
self::assertArrayHasKey($path2, $sourceFiles);
$this->assertSourceFileTest2UnderRootDir($sourceFiles[$path2]);
}
protected function legacySetUp()
{
$this->setUpDir(realpath(__DIR__ . '/../../..'));
$this->object = new CloverXmlCoverageCollector();
}
/**
* @return \SimpleXMLElement
*/
protected function createCloverXml()
{
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1365848893">
<project timestamp="1365848893">
<file name="%s/test.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="2"/>
</file>
<file name="%s/test.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="1"/>
</file>
<file name="dummy.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="0"/>
</file>
<package name="Hoge">
<file name="%s/test2.php">
<class name="TestFile" namespace="Hoge">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="6" type="method" name="__construct" crap="1" count="0"/>
<line num="8" type="stmt" count="0"/>
</file>
<file path="%s/test3.php" name="test3.php">
<class name="TestFileBis" namespace="Hoge">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="6" type="method" name="__construct" crap="1" count="0"/>
<line num="8" type="stmt" count="0"/>
</file>
</package>
</project>
</coverage>
XML;
return simplexml_load_string(sprintf($xml, $this->srcDir, $this->srcDir, $this->srcDir, $this->srcDir));
}
// custom assert
/**
* @param JsonFile $jsonFile
* @param string $serviceName
* @param string $serviceJobId
* @param string $repoToken
* @param Git $git
* @param string $runAt
*/
protected function assertJsonFile($jsonFile, $serviceName, $serviceJobId, $repoToken, $git, $runAt)
{
self::assertSame($serviceName, $jsonFile->getServiceName());
self::assertSame($serviceJobId, $jsonFile->getServiceJobId());
self::assertSame($repoToken, $jsonFile->getRepoToken());
self::assertSame($git, $jsonFile->getGit());
self::assertSame($runAt, $jsonFile->getRunAt());
}
/**
* @param string $name
* @param string $path
* @param int $fileLines
* @param string $source
*/
protected function assertSourceFile(SourceFile $sourceFile, $name, $path, $fileLines, array $coverage, $source)
{
self::assertSame($name, $sourceFile->getName());
$this->assertSamePath($path, $sourceFile->getPath());
self::assertSame($fileLines, $sourceFile->getFileLines());
self::assertSame($coverage, $sourceFile->getCoverage());
self::assertSame($source, $sourceFile->getSource());
}
protected function assertSourceFileTest1(SourceFile $sourceFile)
{
$name1 = 'test.php';
$path1 = $this->srcDir . \DIRECTORY_SEPARATOR . $name1;
$fileLines1 = 9;
$coverage1 = array_fill(0, $fileLines1, null);
$coverage1[6] = 3;
$source1 = trim(file_get_contents($path1));
$this->assertSourceFile($sourceFile, $name1, $path1, $fileLines1, $coverage1, $source1);
}
protected function assertSourceFileTest2(SourceFile $sourceFile)
{
$name2 = 'test2.php';
$path2 = $this->srcDir . \DIRECTORY_SEPARATOR . $name2;
$fileLines2 = 10;
$coverage2 = array_fill(0, $fileLines2, null);
$coverage2[7] = 0;
$source2 = trim(file_get_contents($path2));
$this->assertSourceFile($sourceFile, $name2, $path2, $fileLines2, $coverage2, $source2);
}
protected function assertSourceFileTest1UnderRootDir(SourceFile $sourceFile)
{
$name1 = 'test.php';
$path1 = $this->srcDir . \DIRECTORY_SEPARATOR . $name1;
$fileLines1 = 9;
$coverage1 = array_fill(0, $fileLines1, null);
$coverage1[6] = 3;
$source1 = trim(file_get_contents($path1));
$this->assertSourceFile($sourceFile, $path1, $path1, $fileLines1, $coverage1, $source1);
}
protected function assertSourceFileTest2UnderRootDir(SourceFile $sourceFile)
{
$name2 = 'test2.php';
$path2 = $this->srcDir . \DIRECTORY_SEPARATOR . $name2;
$fileLines2 = 10;
$coverage2 = array_fill(0, $fileLines2, null);
$coverage2[7] = 0;
$source2 = trim(file_get_contents($path2));
$this->assertSourceFile($sourceFile, $path2, $path2, $fileLines2, $coverage2, $source2);
}
}
@@ -0,0 +1,309 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Collector;
use PhpCoveralls\Bundle\CoverallsBundle\Collector\GitInfoCollector;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Remote;
use PhpCoveralls\Component\System\Git\GitCommand;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Collector\GitInfoCollector
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class GitInfoCollectorTest extends ProjectTestCase
{
/**
* @var array
*/
private $getBranchesValue = [
' master',
'* branch-1',
' branch-2',
];
/**
* @var array
*/
private $getHeadCommitValue = [
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'Author Name',
'author@satooshi.jp',
'Committer Name',
'committer@satooshi.jp',
'commit message',
];
/**
* @var array
*/
private $getRemotesValue = [
"origin\tgit@github.com:php-coveralls/php-coveralls.git (fetch)",
"origin\tgit@github.com:php-coveralls/php-coveralls.git (push)",
];
// getCommand()
/**
* @test
*/
public function shouldHaveGitCommandOnConstruction()
{
$command = new GitCommand();
$object = new GitInfoCollector($command);
self::assertSame($command, $object->getCommand());
}
// collect()
/**
* @test
*/
public function shouldCollect()
{
$gitCommand = $this->createGitCommandStubWith($this->getBranchesValue, $this->getHeadCommitValue, $this->getRemotesValue);
$object = new GitInfoCollector($gitCommand);
$git = $object->collect();
self::assertInstanceOf(Git::class, $git);
$this->assertGit($git);
self::assertSame('branch-1', $git->getBranch());
}
/**
* @test
*/
public function shouldCollectDetachedRef()
{
$gitCommand = $this->createGitCommandStubWith(
['* (HEAD detached at pull/1/merge)'],
$this->getHeadCommitValue,
$this->getRemotesValue
);
$object = new GitInfoCollector($gitCommand);
$git = $object->collect();
self::assertInstanceOf(Git::class, $git);
$this->assertGit($git);
self::assertSame('pull/1/merge', $git->getBranch());
}
/**
* @test
*/
public function shouldCollectNoBranch()
{
$gitCommand = $this->createGitCommandStubWith(
['* (no branch)'],
$this->getHeadCommitValue,
$this->getRemotesValue
);
$object = new GitInfoCollector($gitCommand);
$git = $object->collect();
self::assertInstanceOf(Git::class, $git);
$this->assertGit($git);
self::assertSame('(no branch)', $git->getBranch());
}
// collectBranch() exception
/**
* @test
*/
public function throwRuntimeExceptionIfCurrentBranchNotFound()
{
$getBranchesValue = [
' master',
];
$gitCommand = $this->createGitCommandStubCalledBranches($getBranchesValue);
$object = new GitInfoCollector($gitCommand);
$this->expectException(\RuntimeException::class);
$object->collect();
}
// collectCommit() exception
/**
* @test
*/
public function throwRuntimeExceptionIfHeadCommitIsInvalid()
{
$getHeadCommitValue = [];
$gitCommand = $this->createGitCommandStubCalledHeadCommit($this->getBranchesValue, $getHeadCommitValue);
$object = new GitInfoCollector($gitCommand);
$this->expectException(\RuntimeException::class);
$object->collect();
}
// collectRemotes() exception
/**
* @test
*/
public function throwRuntimeExceptionIfRemoteIsInvalid()
{
$getRemotesValue = [];
$gitCommand = $this->createGitCommandStubWith($this->getBranchesValue, $this->getHeadCommitValue, $getRemotesValue);
$object = new GitInfoCollector($gitCommand);
$this->expectException(\RuntimeException::class);
$object->collect();
}
/**
* @param mixed $getBranchesValue
* @param mixed $getHeadCommitValue
* @param mixed $getRemotesValue
*
* @return object
*/
protected function createGitCommandStubWith($getBranchesValue, $getHeadCommitValue, $getRemotesValue)
{
$stub = $this->prophesize(GitCommand::class);
$this->setUpGitCommandStubWithGetBranchesOnce($stub, $getBranchesValue);
$this->setUpGitCommandStubWithGetHeadCommitOnce($stub, $getHeadCommitValue);
$this->setUpGitCommandStubWithGetRemotesOnce($stub, $getRemotesValue);
return $stub->reveal();
}
/**
* @param array $getBranchesValue
*
* @return GitCommand
*/
protected function createGitCommandStubCalledBranches($getBranchesValue)
{
$stub = $this->prophesize(GitCommand::class);
$this->setUpGitCommandStubWithGetBranchesOnce($stub, $getBranchesValue);
$this->setUpGitCommandStubWithGetHeadCommitNeverCalled($stub);
$this->setUpGitCommandStubWithGetRemotesNeverCalled($stub);
return $stub->reveal();
}
/**
* @param array $getBranchesValue
* @param array $getHeadCommitValue
*
* @return GitCommand
*/
protected function createGitCommandStubCalledHeadCommit($getBranchesValue, $getHeadCommitValue)
{
$stub = $this->prophesize(GitCommand::class);
$this->setUpGitCommandStubWithGetBranchesOnce($stub, $getBranchesValue);
$this->setUpGitCommandStubWithGetHeadCommitOnce($stub, $getHeadCommitValue);
$this->setUpGitCommandStubWithGetRemotesNeverCalled($stub);
return $stub->reveal();
}
/**
* @param GitCommand $stub
* @param mixed $getBranchesValue
*/
protected function setUpGitCommandStubWithGetBranchesOnce($stub, $getBranchesValue)
{
$stub
->getBranches()
->willReturn($getBranchesValue)
->shouldBeCalled()
;
}
/**
* @param GitCommand $stub
* @param array $getHeadCommitValue
*/
protected function setUpGitCommandStubWithGetHeadCommitOnce($stub, $getHeadCommitValue)
{
$stub
->getHeadCommit()
->willReturn($getHeadCommitValue)
->shouldBeCalled()
;
}
/**
* @param GitCommand $stub
*/
protected function setUpGitCommandStubWithGetHeadCommitNeverCalled($stub)
{
$stub
->getHeadCommit()
->shouldNotBeCalled()
;
}
/**
* @param GitCommand $stub
* @param array $getRemotesValue
*/
protected function setUpGitCommandStubWithGetRemotesOnce($stub, $getRemotesValue)
{
$stub
->getRemotes()
->willReturn($getRemotesValue)
->shouldBeCalled()
;
}
/**
* @param GitCommand $stub
*/
protected function setUpGitCommandStubWithGetRemotesNeverCalled($stub)
{
$stub
->getRemotes()
->shouldNotBeCalled()
;
}
protected function assertGit(Git $git)
{
$commit = $git->getHead();
self::assertInstanceOf(Commit::class, $commit);
$this->assertCommit($commit);
$remotes = $git->getRemotes();
self::assertCount(1, $remotes);
self::assertInstanceOf(Remote::class, $remotes[0]);
$this->assertRemote($remotes[0]);
}
protected function assertCommit(Commit $commit)
{
self::assertSame('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', $commit->getId());
self::assertSame('Author Name', $commit->getAuthorName());
self::assertSame('author@satooshi.jp', $commit->getAuthorEmail());
self::assertSame('Committer Name', $commit->getCommitterName());
self::assertSame('committer@satooshi.jp', $commit->getCommitterEmail());
self::assertSame('commit message', $commit->getMessage());
}
protected function assertRemote(Remote $remote)
{
self::assertSame('origin', $remote->getName());
self::assertSame('git@github.com:php-coveralls/php-coveralls.git', $remote->getUrl());
}
}
@@ -0,0 +1,248 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Command;
use PhpCoveralls\Bundle\CoverallsBundle\Command\CoverallsJobsCommand;
use PhpCoveralls\Tests\ProjectTestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Command\CoverallsJobsCommand
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class CoverallsJobsCommandTest extends ProjectTestCase
{
/**
* @test
*/
public function shouldExecuteCoverallsJobsCommand()
{
$this->makeProjectDir(null, $this->logsDir);
$this->dumpCloverXml();
$command = new CoverallsJobsCommand();
$command->setRootDir($this->rootDir);
$app = new Application();
$app->add($command);
$command = $app->find('coveralls:v1:jobs');
$commandTester = new CommandTester($command);
$_SERVER['TRAVIS'] = true;
$_SERVER['TRAVIS_BUILD_NUMBER'] = '12345';
$_SERVER['TRAVIS_JOB_ID'] = 'command_test';
$actual = $commandTester->execute(
[
'command' => $command->getName(),
'--dry-run' => true,
'--config' => 'coveralls.yml',
'--env' => 'test',
]
);
self::assertSame(0, $actual);
// It should succeed too with a correct coverage_clover option.
$actual = $commandTester->execute(
[
'command' => $command->getName(),
'--dry-run' => true,
'--config' => 'coveralls.yml',
'--env' => 'test',
'--coverage_clover' => 'build/logs/clover.xml',
]
);
self::assertSame(0, $actual);
}
/**
* @test
*/
public function shouldExecuteCoverallsJobsCommandWithWrongRootDir()
{
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
$this->makeProjectDir(null, $this->logsDir);
$this->dumpCloverXml();
$command = new CoverallsJobsCommand();
$command->setRootDir($this->logsDir); // Wrong rootDir.
$app = new Application();
$app->add($command);
$command = $app->find('coveralls:v1:jobs');
$commandTester = new CommandTester($command);
$_SERVER['TRAVIS'] = true;
$_SERVER['TRAVIS_JOB_ID'] = 'command_test';
$actual = $commandTester->execute(
[
'command' => $command->getName(),
'--dry-run' => true,
'--config' => 'coveralls.yml',
'--env' => 'test',
]
);
self::assertSame(0, $actual);
}
/**
* @test
*/
public function shouldExecuteCoverallsJobsCommandWithRootDirOverride()
{
$this->makeProjectDir(null, $this->logsDir);
$this->dumpCloverXml();
$command = new CoverallsJobsCommand();
$command->setRootDir($this->logsDir); // Wrong rootDir.
$app = new Application();
$app->add($command);
$command = $app->find('coveralls:v1:jobs');
$commandTester = new CommandTester($command);
$_SERVER['TRAVIS'] = true;
$_SERVER['TRAVIS_JOB_ID'] = 'command_test';
$actual = $commandTester->execute(
[
'command' => $command->getName(),
'--dry-run' => true,
'--config' => 'coveralls.yml',
'--env' => 'test',
// Overriding with a correct one.
'--root_dir' => $this->rootDir,
]
);
self::assertSame(0, $actual);
}
/**
* @test
*/
public function shouldExecuteCoverallsJobsCommandThrowInvalidConfigurationException()
{
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
$this->makeProjectDir(null, $this->logsDir);
$this->dumpCloverXml();
$command = new CoverallsJobsCommand();
$command->setRootDir($this->rootDir);
$app = new Application();
$app->add($command);
$command = $app->find('coveralls:v1:jobs');
$commandTester = new CommandTester($command);
$_SERVER['TRAVIS'] = true;
$_SERVER['TRAVIS_JOB_ID'] = 'command_test';
$commandTester->execute(
[
'command' => $command->getName(),
'--dry-run' => true,
'--config' => 'coveralls.yml',
'--env' => 'test',
'--coverage_clover' => 'nonexistent.xml',
]
);
}
/**
* @test
*/
public function shouldExecuteCoverallsJobsCommandWithInsecureOption()
{
$this->makeProjectDir(null, $this->logsDir);
$this->dumpCloverXml();
$command = new CoverallsJobsCommand();
$command->setRootDir($this->rootDir);
$app = new Application();
$app->add($command);
$command = $app->find('coveralls:v1:jobs');
$commandTester = new CommandTester($command);
$_SERVER['TRAVIS'] = true;
$_SERVER['TRAVIS_JOB_ID'] = 'command_test';
$actual = $commandTester->execute(
[
'command' => $command->getName(),
'--dry-run' => true,
'--config' => 'coveralls.yml',
'--env' => 'test',
'--insecure' => true,
]
);
self::assertSame(0, $actual);
}
protected function legacySetUp()
{
$this->setUpDir(realpath(__DIR__ . '/../../..'));
}
protected function legacyTearDown()
{
$this->rmFile($this->cloverXmlPath);
$this->rmFile($this->jsonPath);
$this->rmDir($this->logsDir);
$this->rmDir($this->buildDir);
}
/**
* @return string
*/
protected function getCloverXml()
{
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1365848893">
<project timestamp="1365848893">
<file name="%s/test.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="0"/>
</file>
<package name="Hoge">
<file name="%s/test2.php">
<class name="TestFile" namespace="Hoge">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="6" type="method" name="__construct" crap="1" count="0"/>
<line num="8" type="stmt" count="0"/>
</file>
</package>
</project>
</coverage>
XML;
return sprintf($xml, $this->srcDir, $this->srcDir);
}
protected function dumpCloverXml()
{
file_put_contents($this->cloverXmlPath, $this->getCloverXml());
}
}
@@ -0,0 +1,431 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Config;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class ConfigurationTest extends ProjectTestCase
{
/**
* @var Configuration
*/
private $object;
// hasRepoToken()
// getRepoToken()
/**
* @test
*/
public function shouldNotHaveRepoTokenOnConstruction()
{
self::assertFalse($this->object->hasRepoToken());
self::assertNull($this->object->getRepoToken());
}
// hasServiceName()
// getServiceName()
/**
* @test
*/
public function shouldNotHaveServiceNameOnConstruction()
{
self::assertFalse($this->object->hasServiceName());
self::assertNull($this->object->getServiceName());
}
// getCloverXmlPaths()
/**
* @test
*/
public function shouldHaveEmptyCloverXmlPathsOnConstruction()
{
self::assertEmpty($this->object->getCloverXmlPaths());
}
// getRootDir()
/**
* @test
*/
public function shouldNotRootDirOnConstruction()
{
self::assertNull($this->object->getRootDir());
}
// getJsonPath()
/**
* @test
*/
public function shouldNotHaveJsonPathOnConstruction()
{
self::assertNull($this->object->getJsonPath());
}
// isDryRun()
/**
* @test
*/
public function shouldBeDryRunOnConstruction()
{
self::assertTrue($this->object->isDryRun());
}
// isExcludeNoStatements()
/**
* @test
*/
public function shouldNotBeExcludeNotStatementsOnConstruction()
{
self::assertFalse($this->object->isExcludeNoStatements());
}
// isVerbose
/**
* @test
*/
public function shouldNotBeVerboseOnConstruction()
{
self::assertFalse($this->object->isVerbose());
}
// getEnv()
/**
* @test
*/
public function shouldBeProdEnvOnConstruction()
{
self::assertSame('prod', $this->object->getEnv());
}
// isTestEnv()
/**
* @test
*/
public function shouldBeTestEnv()
{
$expected = 'test';
$this->object->setEnv($expected);
self::assertSame($expected, $this->object->getEnv());
self::assertTrue($this->object->isTestEnv());
self::assertFalse($this->object->isDevEnv());
self::assertFalse($this->object->isProdEnv());
}
// isDevEnv()
/**
* @test
*/
public function shouldBeDevEnv()
{
$expected = 'dev';
$this->object->setEnv($expected);
self::assertSame($expected, $this->object->getEnv());
self::assertFalse($this->object->isTestEnv());
self::assertTrue($this->object->isDevEnv());
self::assertFalse($this->object->isProdEnv());
}
// isProdEnv()
/**
* @test
*/
public function shouldBeProdEnv()
{
$expected = 'prod';
$this->object->setEnv($expected);
self::assertSame($expected, $this->object->getEnv());
self::assertFalse($this->object->isTestEnv());
self::assertFalse($this->object->isDevEnv());
self::assertTrue($this->object->isProdEnv());
}
// setEntryPoint
/**
* @test
*/
public function shouldSetEntryPoint()
{
$expected = 'http://entry_point';
$same = $this->object->setEntryPoint($expected);
self::assertSame($same, $this->object);
self::assertSame($expected, $this->object->getEntryPoint());
}
// setRootDir()
/**
* @test
*/
public function shouldSetRootDir()
{
$expected = '/root';
$same = $this->object->setRootDir($expected);
self::assertSame($same, $this->object);
self::assertSame($expected, $this->object->getRootDir());
}
// setRepoToken()
/**
* @test
*/
public function shouldSetRepoToken()
{
$expected = 'token';
$same = $this->object->setRepoToken($expected);
self::assertSame($same, $this->object);
self::assertSame($expected, $this->object->getRepoToken());
}
// setServiceName()
/**
* @test
*/
public function shouldSetServiceName()
{
$expected = 'travis-ci';
$same = $this->object->setServiceName($expected);
self::assertSame($same, $this->object);
self::assertSame($expected, $this->object->getServiceName());
}
// setCloverXmlPaths()
/**
* @test
*/
public function shouldSetCloverXmlPaths()
{
$expected = ['/path/to/clover.xml'];
$same = $this->object->setCloverXmlPaths($expected);
self::assertSame($same, $this->object);
self::assertSame($expected, $this->object->getCloverXmlPaths());
}
// addCloverXmlPath()
/**
* @test
*/
public function shouldAddCloverXmlPath()
{
$expected = '/path/to/clover.xml';
$same = $this->object->addCloverXmlPath($expected);
self::assertSame($same, $this->object);
self::assertSame([$expected], $this->object->getCloverXmlPaths());
}
// setJsonPath()
/**
* @test
*/
public function shouldSetJsonPath()
{
$expected = '/path/to/coveralls-upload.json';
$same = $this->object->setJsonPath($expected);
self::assertSame($same, $this->object);
self::assertSame($expected, $this->object->getJsonPath());
}
// setDryRun()
/**
* @test
*/
public function shouldSetDryRunFalse()
{
$expected = false;
$same = $this->object->setDryRun($expected);
self::assertSame($same, $this->object);
self::assertFalse($this->object->isDryRun());
}
/**
* @test
*/
public function shouldSetDryRunTrue()
{
$expected = true;
$same = $this->object->setDryRun($expected);
self::assertSame($same, $this->object);
self::assertTrue($this->object->isDryRun());
}
// setExcludeNoStatements()
/**
* @test
*/
public function shouldSetExcludeNoStatementsFalse()
{
$expected = false;
$same = $this->object->setExcludeNoStatements($expected);
self::assertSame($same, $this->object);
self::assertFalse($this->object->isExcludeNoStatements());
}
/**
* @test
*/
public function shouldSetExcludeNoStatementsTrue()
{
$expected = true;
$same = $this->object->setExcludeNoStatements($expected);
self::assertSame($same, $this->object);
self::assertTrue($this->object->isExcludeNoStatements());
}
// setExcludeNoStatementsUnlessFalse()
/**
* @test
*/
public function shouldSetExcludeNoStatementsFalseUnlessFalse()
{
$expected = false;
$same = $this->object->setExcludeNoStatementsUnlessFalse($expected);
self::assertSame($same, $this->object);
self::assertFalse($this->object->isExcludeNoStatements());
}
/**
* @test
*/
public function shouldSetExcludeNoStatementsTrueUnlessFalse()
{
$expected = true;
$same = $this->object->setExcludeNoStatementsUnlessFalse($expected);
self::assertSame($same, $this->object);
self::assertTrue($this->object->isExcludeNoStatements());
}
/**
* @test
*/
public function shouldSetExcludeNoStatementsTrueIfFalsePassedAndIfTrueWasSet()
{
$expected = false;
$same = $this->object->setExcludeNoStatements(true);
$same = $this->object->setExcludeNoStatementsUnlessFalse($expected);
self::assertSame($same, $this->object);
self::assertTrue($this->object->isExcludeNoStatements());
}
/**
* @test
*/
public function shouldSetExcludeNoStatementsTrueIfTruePassedAndIfTrueWasSet()
{
$expected = true;
$same = $this->object->setExcludeNoStatements(true);
$same = $this->object->setExcludeNoStatementsUnlessFalse($expected);
self::assertSame($same, $this->object);
self::assertTrue($this->object->isExcludeNoStatements());
}
// setVerbose()
/**
* @test
*/
public function shouldSetVerboseFalse()
{
$expected = false;
$same = $this->object->setVerbose($expected);
self::assertSame($same, $this->object);
self::assertFalse($this->object->isVerbose());
}
/**
* @test
*/
public function shouldSetVerboseTrue()
{
$expected = true;
$same = $this->object->setVerbose($expected);
self::assertSame($same, $this->object);
self::assertTrue($this->object->isVerbose());
}
// setEnv()
/**
* @test
*/
public function shouldSetEnv()
{
$expected = 'myenv';
$same = $this->object->setEnv($expected);
self::assertSame($same, $this->object);
self::assertSame($expected, $this->object->getEnv());
}
protected function legacySetUp()
{
$this->object = new Configuration();
}
}
@@ -0,0 +1,426 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Config;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configurator;
use PhpCoveralls\Tests\ProjectTestCase;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Config\Configurator
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Config\CoverallsConfiguration
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class ConfiguratorTest extends ProjectTestCase
{
/**
* @var Configurator
*/
private $object;
// load()
/**
* @test
*/
public function shouldLoadNonExistingYml()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/dummy.yml');
$config = $this->object->load($path, $this->rootDir);
$this->assertConfiguration($config, [$this->cloverXmlPath], $this->jsonPath);
}
// default src_dir not found, it doesn't throw anything now, as src_dir is not required for configuration
/**
* @test
*/
public function loadConfigurationOnLoadEmptyYmlWhenSrcDirNotFound()
{
$this->makeProjectDir(null, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/dummy.yml');
$config = $this->object->load($path, $this->rootDir);
self::assertInstanceOf('PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration', $config);
}
// default coverage_clover not found
/**
* @test
*/
public function throwInvalidConfigurationExceptionOnLoadEmptyYmlIfCoverageCloverNotFound()
{
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
$this->makeProjectDir($this->srcDir, $this->logsDir, null);
$path = realpath(__DIR__ . '/yaml/dummy.yml');
$this->object->load($path, $this->rootDir);
}
// default json_path not writable
/**
* @test
*/
public function throwInvalidConfigurationExceptionOnLoadEmptyYmlIfJsonPathDirNotWritable()
{
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
if ($this->isWindowsOS()) {
// On Windows read-only attribute on dir applies to files in dir, but not the dir itself.
self::markTestSkipped('Unable to run on Windows');
}
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath, true);
$path = realpath(__DIR__ . '/yaml/dummy.yml');
$this->object->load($path, $this->rootDir);
}
/**
* @test
*/
public function throwInvalidConfigurationExceptionOnLoadEmptyYmlIfJsonPathNotWritable()
{
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath, false, true);
$path = realpath(__DIR__ . '/yaml/dummy.yml');
$this->object->load($path, $this->rootDir);
}
// no configuration
/**
* @test
*/
public function shouldLoadEmptyYml()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/empty.yml');
$config = $this->object->load($path, $this->rootDir);
$this->assertConfiguration($config, [$this->cloverXmlPath], $this->jsonPath);
}
// load default value yml
/**
* @test
*/
public function shouldThrowInvalidConfigurationExceptionUponLoadingSrcDirYml()
{
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/src_dir.yml');
$this->object->load($path, $this->rootDir);
}
/**
* @test
*/
public function shouldLoadCoverageCloverYmlContainingDefaultValue()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/coverage_clover.yml');
$config = $this->object->load($path, $this->rootDir);
$this->assertConfiguration($config, [$this->cloverXmlPath], $this->jsonPath);
}
/**
* @test
*/
public function shouldLoadCoverageCloverOverriddenByInput()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, [$this->cloverXmlPath1, $this->cloverXmlPath2]);
$path = realpath(__DIR__ . '/yaml/coverage_clover.yml');
// Mocking command line options.
$defs = new InputDefinition(
[
new InputOption(
'coverage_clover',
'x',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY
),
]
);
$inputArray = [
'--coverage_clover' => [
'build/logs/clover-part1.xml',
'build/logs/clover-part2.xml',
],
];
$input = new ArrayInput($inputArray, $defs);
$config = $this->object->load($path, $this->rootDir, $input);
$this->assertConfiguration($config, [$this->cloverXmlPath1, $this->cloverXmlPath2], $this->jsonPath);
}
/**
* @test
*/
public function shouldLoadCoverageCloverYmlContainingGlobValue()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, [$this->cloverXmlPath1, $this->cloverXmlPath2]);
$path = realpath(__DIR__ . '/yaml/coverage_clover_glob.yml');
$config = $this->object->load($path, $this->rootDir);
$this->assertConfiguration($config, [$this->cloverXmlPath1, $this->cloverXmlPath2], $this->jsonPath);
}
/**
* @test
*/
public function shouldLoadCoverageCloverYmlContainingArrayValue()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, [$this->cloverXmlPath1, $this->cloverXmlPath2]);
$path = realpath(__DIR__ . '/yaml/coverage_clover_array.yml');
$config = $this->object->load($path, $this->rootDir);
$this->assertConfiguration($config, [$this->cloverXmlPath1, $this->cloverXmlPath2], $this->jsonPath);
}
/**
* @test
*/
public function shouldLoadJsonPathYmlContainingDefaultValue()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/json_path.yml');
$config = $this->object->load($path, $this->rootDir);
$this->assertConfiguration($config, [$this->cloverXmlPath], $this->jsonPath);
}
/**
* @test
*/
public function shouldLoadJsonPathOverriddenByInput()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/json_path.yml');
// Mocking command line options.
$defs = new InputDefinition(
[
new InputOption(
'json_path',
'o',
InputOption::VALUE_REQUIRED
),
]
);
$inputArray = [
'--json_path' => 'build/logs/coveralls-upload-custom.json',
];
$expectedJsonPath = substr($this->jsonPath, 0, \strlen($this->jsonPath) - 5) . '-custom.json';
$input = new ArrayInput($inputArray, $defs);
$config = $this->object->load($path, $this->rootDir, $input);
$this->assertConfiguration($config, [$this->cloverXmlPath], $expectedJsonPath);
}
/**
* @test
*/
public function shouldLoadExcludeNoStmtYmlContainingTrue()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/exclude_no_stmt_true.yml');
$config = $this->object->load($path, $this->rootDir);
$this->assertConfiguration($config, [$this->cloverXmlPath], $this->jsonPath, true);
}
/**
* @test
*/
public function shouldLoadExcludeNoStmtYmlContainingFalse()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/exclude_no_stmt_false.yml');
$config = $this->object->load($path, $this->rootDir);
$this->assertConfiguration($config, [$this->cloverXmlPath], $this->jsonPath, false);
}
/**
* @test
*/
public function shouldLoadEntryPoint()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/entry_point.yml');
$config = $this->object->load($path, $this->rootDir);
self::assertSame('http://foo.bar', $config->getEntryPoint());
}
/**
* @test
*/
public function shouldLoadUseDefaultEntryPointIfNotSet()
{
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/empty.yml');
$config = $this->object->load($path, $this->rootDir);
self::assertSame('https://coveralls.io', $config->getEntryPoint());
}
// configured coverage_clover not found
/**
* @test
*/
public function throwInvalidConfigurationExceptionOnLoadCoverageCloverYmlIfCoverageCloverNotFound()
{
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/coverage_clover_not_found.yml');
$this->object->load($path, $this->rootDir);
}
/**
* @test
*/
public function throwInvalidConfigurationExceptionOnLoadCoverageCloverYmlIfCoverageCloverIsNotString()
{
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/coverage_clover_invalid.yml');
$this->object->load($path, $this->rootDir);
}
// configured json_path not found
/**
* @test
*/
public function throwInvalidConfigurationExceptionOnLoadJsonPathYmlIfJsonPathNotFound()
{
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/json_path_not_found.yml');
$this->object->load($path, $this->rootDir);
}
// configured exclude_no_stmt invalid
/**
* @test
*/
public function throwInvalidConfigurationExceptionOnLoadExcludeNoStmtYmlIfInvalid()
{
if (\PHP_VERSION_ID >= 80000 && !\function_exists('get_debug_type')) {
self::markTestIncomplete('get_debug_type() is not available yet');
}
$this->expectException(\Symfony\Component\Config\Definition\Exception\InvalidConfigurationException::class);
$this->makeProjectDir($this->srcDir, $this->logsDir, $this->cloverXmlPath);
$path = realpath(__DIR__ . '/yaml/exclude_no_stmt_invalid.yml');
$this->object->load($path, $this->rootDir);
}
protected function legacySetUp()
{
$this->setUpDir(realpath(__DIR__ . '/../../..'));
$this->srcDir = $this->rootDir . '/src';
$this->object = new Configurator();
}
protected function legacyTearDown()
{
$this->rmFile($this->cloverXmlPath);
$this->rmFile($this->cloverXmlPath1);
$this->rmFile($this->cloverXmlPath2);
$this->rmFile($this->jsonPath);
$this->rmDir($this->srcDir);
$this->rmDir($this->logsDir);
$this->rmDir($this->buildDir);
}
// custom assertion
/**
* @param string $jsonPath
* @param bool $excludeNoStatements
*/
protected function assertConfiguration(Configuration $config, array $cloverXml, $jsonPath, $excludeNoStatements = false)
{
$this->assertSamePaths($cloverXml, $config->getCloverXmlPaths());
$this->assertSamePath($jsonPath, $config->getJsonPath());
self::assertSame($excludeNoStatements, $config->isExcludeNoStatements());
}
/**
* @return bool
*/
private function isWindowsOS()
{
static $isWindows;
if ($isWindows === null) {
$isWindows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
}
return $isWindows;
}
}
@@ -0,0 +1 @@
coverage_clover: build/logs/clover.xml
@@ -0,0 +1,3 @@
coverage_clover:
- build/logs/clover-part1.xml
- build/logs/clover-part2.xml
@@ -0,0 +1 @@
coverage_clover: build/logs/clover-*.xml
@@ -0,0 +1 @@
coverage_clover: build/logs/clover.dummy.xml
@@ -0,0 +1 @@
entry_point: http://foo.bar/
@@ -0,0 +1 @@
json_path: build/logs/coveralls-upload.json
@@ -0,0 +1 @@
json_path: build/logs.dummy/coveralls-upload.json
@@ -0,0 +1,96 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Console;
use PhpCoveralls\Bundle\CoverallsBundle\Console\ApplicationFactory;
use PhpCoveralls\Tests\ProjectTestCase;
use Symfony\Component\Console\Tester\ApplicationTester;
/**
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*
* @info This test is also covering \PhpCoveralls\Bundle\CoverallsBundle\Console\Application, but we cannot declare it as if so, PCOV will autoload it and it will fail on non-compat Sf/PHP combination, as we cannot declare partial loading anymore
*
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Console\ApplicationFactory
*/
final class ApplicationTest extends ProjectTestCase
{
/**
* @test
*/
public function shouldExecuteCoverallsJobsCommand()
{
$this->makeProjectDir(null, $this->logsDir);
$this->dumpCloverXml();
$app = ApplicationFactory::create($this->rootDir);
$app->setAutoExit(false); // avoid to call exit() in Application
// run
$_SERVER['TRAVIS'] = true;
$_SERVER['TRAVIS_BUILD_NUMBER'] = 'application_build';
$_SERVER['TRAVIS_JOB_ID'] = 'application_test';
$tester = new ApplicationTester($app);
$actual = $tester->run(
[
'--dry-run' => true,
'--config' => 'coveralls.yml',
]
);
self::assertSame(0, $actual);
}
protected function legacySetUp()
{
$this->setUpDir(realpath(__DIR__ . '/../../..'));
}
protected function legacyTearDown()
{
$this->rmFile($this->cloverXmlPath);
$this->rmFile($this->jsonPath);
$this->rmDir($this->logsDir);
$this->rmDir($this->buildDir);
}
/**
* @return string
*/
protected function getCloverXml()
{
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1365848893">
<project timestamp="1365848893">
<file name="%s/test.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="0"/>
</file>
<package name="Hoge">
<file name="%s/test2.php">
<class name="TestFile" namespace="Hoge">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="6" type="method" name="__construct" crap="1" count="0"/>
<line num="8" type="stmt" count="0"/>
</file>
</package>
</project>
</coverage>
XML;
return sprintf($xml, $this->srcDir, $this->srcDir);
}
protected function dumpCloverXml()
{
file_put_contents($this->cloverXmlPath, $this->getCloverXml());
}
}
@@ -0,0 +1,138 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Entity\Exception;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Exception\RequirementsNotSatisfiedException;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Exception\RequirementsNotSatisfiedException
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class RequirementsNotSatisfiedExceptionTest extends ProjectTestCase
{
// getReadEnv()
/**
* @test
*/
public function shouldNotHaveReadEnvOnConstruction()
{
$object = new RequirementsNotSatisfiedException();
self::assertNull($object->getReadEnv());
}
// setReadEnv()
/**
* @test
*/
public function shouldSetReadEnv()
{
$expected = [
'ENV_NAME' => 'value',
];
$object = new RequirementsNotSatisfiedException();
$object->setReadEnv($expected);
self::assertSame($expected, $object->getReadEnv());
}
// getHelpMessage()
/**
* @test
*/
public function shouldGetHelpMessageWithStringEnvVar()
{
$expected = [
'ENV_NAME' => 'value',
];
$object = new RequirementsNotSatisfiedException();
$object->setReadEnv($expected);
$message = $object->getHelpMessage();
self::assertStringContainsString(" - ENV_NAME='value'", $message);
}
// getHelpMessage()
/**
* @test
*/
public function shouldGetHelpMessageWithSecretStringEnvVarHidden()
{
// Make sure the secret repo token is HIDDEN.
$env = [
'COVERALLS_REPO_TOKEN' => 'secret',
];
$expected = " - COVERALLS_REPO_TOKEN='********(HIDDEN)'";
$object = new RequirementsNotSatisfiedException();
$object->setReadEnv($env);
$message = $object->getHelpMessage();
self::assertStringContainsString($expected, $message);
}
/**
* @test
*/
public function shouldGetHelpMessageWithSecretEmptyStringEnvVarShown()
{
// Make sure the secret repo token is shown when it's empty.
$env = [
'COVERALLS_REPO_TOKEN' => '',
];
$expected = " - COVERALLS_REPO_TOKEN=''";
$object = new RequirementsNotSatisfiedException();
$object->setReadEnv($env);
$message = $object->getHelpMessage();
self::assertStringContainsString($expected, $message);
}
/**
* @test
*/
public function shouldGetHelpMessageWithIntegerEnvVar()
{
$expected = [
'ENV_NAME' => 123,
];
$object = new RequirementsNotSatisfiedException();
$object->setReadEnv($expected);
$message = $object->getHelpMessage();
self::assertStringContainsString(' - ENV_NAME=123', $message);
}
/**
* @test
*/
public function shouldGetHelpMessageWithBooleanEnvVar()
{
$expected = [
'ENV_NAME' => true,
];
$object = new RequirementsNotSatisfiedException();
$object->setReadEnv($expected);
$message = $object->getHelpMessage();
self::assertStringContainsString(' - ENV_NAME=true', $message);
}
}
@@ -0,0 +1,231 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Entity\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Coveralls
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class CommitTest extends ProjectTestCase
{
/**
* @var Commit
*/
private $object;
// getId()
/**
* @test
*/
public function shouldNotHaveIdOnConstruction()
{
self::assertNull($this->object->getId());
}
// getAuthorName()
/**
* @test
*/
public function shouldNotHoveAuthorNameOnConstruction()
{
self::assertNull($this->object->getAuthorName());
}
// getAuthorEmail()
/**
* @test
*/
public function shouldNotHoveAuthorEmailOnConstruction()
{
self::assertNull($this->object->getAuthorEmail());
}
// getCommitterName()
/**
* @test
*/
public function shouldNotHoveCommitterNameOnConstruction()
{
self::assertNull($this->object->getCommitterName());
}
// getCommitterEmail()
/**
* @test
*/
public function shouldNotHoveCommitterEmailOnConstruction()
{
self::assertNull($this->object->getCommitterEmail());
}
// getMessage()
/**
* @test
*/
public function shouldNotHoveMessageOnConstruction()
{
self::assertNull($this->object->getMessage());
}
// setId()
/**
* @test
*/
public function shouldSetId()
{
$expected = 'id';
$obj = $this->object->setId($expected);
self::assertSame($expected, $this->object->getId());
self::assertSame($obj, $this->object);
}
// setAuthorName()
/**
* @test
*/
public function shouldSetAuthorName()
{
$expected = 'author_name';
$obj = $this->object->setAuthorName($expected);
self::assertSame($expected, $this->object->getAuthorName());
self::assertSame($obj, $this->object);
}
// setAuthorEmail()
/**
* @test
*/
public function shouldSetAuthorEmail()
{
$expected = 'author_email';
$obj = $this->object->setAuthorEmail($expected);
self::assertSame($expected, $this->object->getAuthorEmail());
self::assertSame($obj, $this->object);
}
// setCommitterName()
/**
* @test
*/
public function shouldSetCommitterName()
{
$expected = 'committer_name';
$obj = $this->object->setCommitterName($expected);
self::assertSame($expected, $this->object->getCommitterName());
self::assertSame($obj, $this->object);
}
// setCommitterEmail()
/**
* @test
*/
public function shouldSetCommitterEmail()
{
$expected = 'committer_email';
$obj = $this->object->setCommitterEmail($expected);
self::assertSame($expected, $this->object->getCommitterEmail());
self::assertSame($obj, $this->object);
}
// setMessage()
/**
* @test
*/
public function shouldSetMessage()
{
$expected = 'message';
$obj = $this->object->setMessage($expected);
self::assertSame($expected, $this->object->getMessage());
self::assertSame($obj, $this->object);
}
// toArray()
/**
* @test
*/
public function shouldConvertToArray()
{
$expected = [
'id' => null,
'author_name' => null,
'author_email' => null,
'committer_name' => null,
'committer_email' => null,
'message' => null,
];
self::assertSame($expected, $this->object->toArray());
self::assertSame(json_encode($expected), (string) $this->object);
}
/**
* @test
*/
public function shouldConvertToFilledArray()
{
$id = 'id';
$authorName = 'author_name';
$authorEmail = 'author_email';
$committerName = 'committer_name';
$committerEmail = 'committer_email';
$message = 'message';
$this->object
->setId($id)
->setAuthorName($authorName)
->setAuthorEmail($authorEmail)
->setCommitterName($committerName)
->setCommitterEmail($committerEmail)
->setMessage($message)
;
$expected = [
'id' => $id,
'author_name' => $authorName,
'author_email' => $authorEmail,
'committer_name' => $committerName,
'committer_email' => $committerEmail,
'message' => $message,
];
self::assertSame($expected, $this->object->toArray());
self::assertSame(json_encode($expected), (string) $this->object);
}
protected function legacySetUp()
{
$this->object = new Commit();
}
}
@@ -0,0 +1,135 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Entity\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Remote;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Coveralls
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Git
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class GitTest extends ProjectTestCase
{
/**
* @var string
*/
private $branchName;
/**
* @var Commit
*/
private $commit;
/**
* @var Remote
*/
private $remote;
/**
* @var Git
*/
private $object;
// getBranch()
/**
* @test
*/
public function shouldHaveBranchNameOnConstruction()
{
self::assertSame($this->branchName, $this->object->getBranch());
}
// getHead()
/**
* @test
*/
public function shouldHaveHeadCommitOnConstruction()
{
self::assertSame($this->commit, $this->object->getHead());
}
// getRemotes()
/**
* @test
*/
public function shouldHaveRemotesOnConstruction()
{
self::assertSame([$this->remote], $this->object->getRemotes());
}
// toArray()
/**
* @test
*/
public function shouldConvertToArray()
{
$expected = [
'branch' => $this->branchName,
'head' => $this->commit->toArray(),
'remotes' => [$this->remote->toArray()],
];
self::assertSame($expected, $this->object->toArray());
self::assertSame(json_encode($expected), (string) $this->object);
}
protected function legacySetUp()
{
$this->branchName = 'branch_name';
$this->commit = $this->createCommit();
$this->remote = $this->createRemote();
$this->object = new Git($this->branchName, $this->commit, [$this->remote]);
}
/**
* @param string $name
* @param string $url
*
* @return Remote
*/
protected function createRemote($name = 'name', $url = 'url')
{
$remote = new Remote();
return $remote
->setName($name)
->setUrl($url)
;
}
/**
* @param string $id
* @param string $authorName
* @param string $authorEmail
* @param string $committerName
* @param string $committerEmail
* @param string $message
*
* @return Commit
*/
protected function createCommit($id = 'id', $authorName = 'author_name', $authorEmail = 'author_email', $committerName = 'committer_name', $committerEmail = 'committer_email', $message = 'message')
{
$commit = new Commit();
return $commit
->setId($id)
->setAuthorName($authorName)
->setAuthorEmail($authorEmail)
->setCommitterName($committerName)
->setCommitterEmail($committerEmail)
->setMessage($message)
;
}
}
@@ -0,0 +1,115 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Entity\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Remote;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Coveralls
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Remote
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class RemoteTest extends ProjectTestCase
{
/**
* @var Remote
*/
private $object;
// getName()
/**
* @test
*/
public function shouldNotHaveRemoteNameOnConstruction()
{
self::assertNull($this->object->getName());
}
// getUrl()
/**
* @test
*/
public function shouldNotHaveUrlOnConstruction()
{
self::assertNull($this->object->getUrl());
}
// setName()
/**
* @test
*/
public function shouldSetRemoteName()
{
$expected = 'remote_name';
$obj = $this->object->setName($expected);
self::assertSame($expected, $this->object->getName());
self::assertSame($obj, $this->object);
}
// setUrl()
/**
* @test
*/
public function shouldSetRemoteUrl()
{
$expected = 'git@github.com:php-coveralls/php-coveralls.git';
$obj = $this->object->setUrl($expected);
self::assertSame($expected, $this->object->getUrl());
self::assertSame($obj, $this->object);
}
// toArray()
/**
* @test
*/
public function shouldConvertToArray()
{
$expected = [
'name' => null,
'url' => null,
];
self::assertSame($expected, $this->object->toArray());
self::assertSame(json_encode($expected), (string) $this->object);
}
/**
* @test
*/
public function shouldConvertToFilledArray()
{
$name = 'name';
$url = 'url';
$this->object
->setName($name)
->setUrl($url)
;
$expected = [
'name' => $name,
'url' => $url,
];
self::assertSame($expected, $this->object->toArray());
self::assertSame(json_encode($expected), (string) $this->object);
}
protected function legacySetUp()
{
$this->object = new Remote();
}
}
@@ -0,0 +1,866 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Entity;
use PhpCoveralls\Bundle\CoverallsBundle\Collector\CloverXmlCoverageCollector;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Commit;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Git;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Git\Remote;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile;
use PhpCoveralls\Bundle\CoverallsBundle\Version;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Coveralls
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class JsonFileTest extends ProjectTestCase
{
/**
* @var JsonFile
*/
private $object;
// hasSourceFile()
// getSourceFile()
/**
* @test
*/
public function shouldNotHaveSourceFileOnConstruction()
{
$path = 'test.php';
self::assertFalse($this->object->hasSourceFile($path));
self::assertNull($this->object->getSourceFile($path));
}
// hasSourceFiles()
// getSourceFiles()
/**
* @test
*/
public function shouldCountZeroSourceFilesOnConstruction()
{
self::assertFalse($this->object->hasSourceFiles());
self::assertEmpty($this->object->getSourceFiles());
}
// getServiceName()
/**
* @test
*/
public function shouldNotHaveServiceNameOnConstruction()
{
self::assertNull($this->object->getServiceName());
}
// getRepoToken()
/**
* @test
*/
public function shouldNotHaveRepoTokenOnConstruction()
{
self::assertNull($this->object->getRepoToken());
}
// getServiceJobId()
/**
* @test
*/
public function shouldNotHaveServiceJobIdOnConstruction()
{
self::assertNull($this->object->getServiceJobId());
}
// getServiceNumber()
/**
* @test
*/
public function shouldNotHaveServiceNumberOnConstruction()
{
self::assertNull($this->object->getServiceNumber());
}
// getServiceEventType()
/**
* @test
*/
public function shouldNotHaveServiceEventTypeOnConstruction()
{
self::assertNull($this->object->getServiceEventType());
}
// getServiceBuildUrl()
/**
* @test
*/
public function shouldNotHaveServiceBuildUrlOnConstruction()
{
self::assertNull($this->object->getServiceBuildUrl());
}
// getServiceBranch()
/**
* @test
*/
public function shouldNotHaveServiceBranchOnConstruction()
{
self::assertNull($this->object->getServiceBranch());
}
// getServicePullRequest()
/**
* @test
*/
public function shouldNotHaveServicePullRequestOnConstruction()
{
self::assertNull($this->object->getServicePullRequest());
}
// getGit()
/**
* @test
*/
public function shouldNotHaveGitOnConstruction()
{
self::assertNull($this->object->getGit());
}
// getRunAt()
/**
* @test
*/
public function shouldNotHaveRunAtOnConstruction()
{
self::assertNull($this->object->getRunAt());
}
// getMetrics()
/**
* @test
*/
public function shouldHaveEmptyMetrics()
{
$metrics = $this->object->getMetrics();
self::assertSame(0, $metrics->getStatements());
self::assertSame(0, $metrics->getCoveredStatements());
self::assertSame(0, $metrics->getLineCoverage());
}
// setServiceName()
/**
* @test
*/
public function shouldSetServiceName()
{
$expected = 'travis-ci';
$obj = $this->object->setServiceName($expected);
self::assertSame($expected, $this->object->getServiceName());
self::assertSame($obj, $this->object);
return $this->object;
}
// setRepoToken()
/**
* @test
*/
public function shouldSetRepoToken()
{
$expected = 'token';
$obj = $this->object->setRepoToken($expected);
self::assertSame($expected, $this->object->getRepoToken());
self::assertSame($obj, $this->object);
return $this->object;
}
// setParallel()
/**
* @test
*/
public function shouldSetParallel()
{
$expected = true;
$obj = $this->object->setParallel($expected);
self::assertSame($expected, $this->object->getParallel());
self::assertSame($obj, $this->object);
return $this->object;
}
// setFlagName()
/**
* @test
*/
public function shouldSetFlagName()
{
$expected = 'php-7.4';
$obj = $this->object->setFlagName($expected);
self::assertSame($expected, $this->object->getFlagName());
self::assertSame($obj, $this->object);
return $this->object;
}
// setServiceJobId()
/**
* @test
*/
public function shouldSetServiceJobId()
{
$expected = 'job_id';
$obj = $this->object->setServiceJobId($expected);
self::assertSame($expected, $this->object->getServiceJobId());
self::assertSame($obj, $this->object);
return $this->object;
}
// setGit()
/**
* @test
*/
public function shouldSetGit()
{
$remotes = [new Remote()];
$head = new Commit();
$git = new Git('master', $head, $remotes);
$obj = $this->object->setGit($git);
self::assertSame($git, $this->object->getGit());
self::assertSame($obj, $this->object);
return $this->object;
}
// setRunAt()
/**
* @test
*/
public function shouldSetRunAt()
{
$expected = '2013-04-04 11:22:33 +0900';
$obj = $this->object->setRunAt($expected);
self::assertSame($expected, $this->object->getRunAt());
self::assertSame($obj, $this->object);
return $this->object;
}
// addSourceFile()
// sortSourceFiles()
/**
* @test
*/
public function shouldAddSourceFile()
{
$sourceFile = $this->createSourceFile();
$this->object->addSourceFile($sourceFile);
$this->object->sortSourceFiles();
$path = $sourceFile->getPath();
self::assertTrue($this->object->hasSourceFiles());
self::assertSame([$path => $sourceFile], $this->object->getSourceFiles());
self::assertTrue($this->object->hasSourceFile($path));
self::assertSame($sourceFile, $this->object->getSourceFile($path));
}
// toArray()
/**
* @test
*/
public function shouldConvertToArray()
{
$expected = [
'source_files' => [],
'environment' => ['packagist_version' => Version::VERSION],
];
self::assertSame($expected, $this->object->toArray());
self::assertSame(json_encode($expected), (string) $this->object);
}
/**
* @test
*/
public function shouldConvertToArrayWithSourceFiles()
{
$sourceFile = $this->createSourceFile();
$this->object->addSourceFile($sourceFile);
$expected = [
'source_files' => [$sourceFile->toArray()],
'environment' => ['packagist_version' => Version::VERSION],
];
self::assertSame($expected, $this->object->toArray());
self::assertSame(json_encode($expected), (string) $this->object);
}
// service_name
/**
* @test
*
* @depends shouldSetServiceName
*
* @param mixed $object
*/
public function shouldConvertToArrayWithServiceName($object)
{
$item = 'travis-ci';
$expected = [
'service_name' => $item,
'source_files' => [],
'environment' => ['packagist_version' => Version::VERSION],
];
self::assertSame($expected, $object->toArray());
self::assertSame(json_encode($expected), (string) $object);
}
// service_job_id
/**
* @test
*
* @depends shouldSetServiceJobId
*
* @param mixed $object
*/
public function shouldConvertToArrayWithServiceJobId($object)
{
$item = 'job_id';
$expected = [
'service_job_id' => $item,
'source_files' => [],
'environment' => ['packagist_version' => Version::VERSION],
];
self::assertSame($expected, $object->toArray());
self::assertSame(json_encode($expected), (string) $object);
}
// repo_token
/**
* @test
*
* @depends shouldSetRepoToken
*
* @param mixed $object
*/
public function shouldConvertToArrayWithRepoToken($object)
{
$item = 'token';
$expected = [
'repo_token' => $item,
'source_files' => [],
'environment' => ['packagist_version' => Version::VERSION],
];
self::assertSame($expected, $object->toArray());
self::assertSame(json_encode($expected), (string) $object);
}
// parallel
/**
* @test
*
* @depends shouldSetParallel
*
* @param mixed $object
*/
public function shouldConvertToArrayWithParallel($object)
{
$item = true;
$expected = [
'parallel' => $item,
'source_files' => [],
'environment' => ['packagist_version' => Version::VERSION],
];
self::assertSame($expected, $object->toArray());
self::assertSame(json_encode($expected), (string) $object);
}
// git
/**
* @test
*
* @depends shouldSetGit
*
* @param mixed $object
*/
public function shouldConvertToArrayWithGit($object)
{
$remotes = [new Remote()];
$head = new Commit();
$git = new Git('master', $head, $remotes);
$expected = [
'git' => $git->toArray(),
'source_files' => [],
'environment' => ['packagist_version' => Version::VERSION],
];
self::assertSame($expected, $object->toArray());
self::assertSame(json_encode($expected), (string) $object);
}
// run_at
/**
* @test
*
* @depends shouldSetRunAt
*
* @param mixed $object
*/
public function shouldConvertToArrayWithRunAt($object)
{
$item = '2013-04-04 11:22:33 +0900';
$expected = [
'run_at' => $item,
'source_files' => [],
'environment' => ['packagist_version' => Version::VERSION],
];
self::assertSame($expected, $object->toArray());
self::assertSame(json_encode($expected), (string) $object);
}
// fillJobs()
/**
* @test
*/
public function shouldFillJobsForServiceJobId()
{
$serviceName = 'travis-ci';
$serviceJobId = '1.1';
$serviceBuild = 123;
$env = [];
$env['CI_NAME'] = $serviceName;
$env['CI_JOB_ID'] = $serviceJobId;
$env['CI_BUILD_NUMBER'] = $serviceBuild;
$object = $this->collectJsonFile();
$same = $object->fillJobs($env);
self::assertSame($same, $object);
self::assertSame($serviceName, $object->getServiceName());
self::assertSame($serviceJobId, $object->getServiceJobId());
self::assertSame($serviceBuild, $object->getServiceNumber());
}
/**
* @test
*/
public function shouldFillJobsForServiceNumber()
{
$repoToken = 'token';
$serviceName = 'circleci';
$serviceNumber = '123';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = $repoToken;
$env['CI_NAME'] = $serviceName;
$env['CI_BUILD_NUMBER'] = $serviceNumber;
$object = $this->collectJsonFile();
$same = $object->fillJobs($env);
self::assertSame($same, $object);
self::assertSame($repoToken, $object->getRepoToken());
self::assertSame($serviceName, $object->getServiceName());
self::assertSame($serviceNumber, $object->getServiceNumber());
}
/**
* @test
*/
public function shouldFillJobsForStandardizedEnvVars()
{
/*
* CI_NAME=codeship
* CI_BUILD_NUMBER=108821
* CI_BUILD_URL=https://www.codeship.io/projects/2777/builds/108821
* CI_BRANCH=master
* CI_PULL_REQUEST=false
*/
$repoToken = 'token';
$parallel = true;
$serviceName = 'codeship';
$serviceNumber = '108821';
$serviceBuildUrl = 'https://www.codeship.io/projects/2777/builds/108821';
$serviceBranch = 'master';
$servicePullRequest = 'false';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = $repoToken;
$env['COVERALLS_PARALLEL'] = $parallel;
$env['CI_NAME'] = $serviceName;
$env['CI_BUILD_NUMBER'] = $serviceNumber;
$env['CI_BUILD_URL'] = $serviceBuildUrl;
$env['CI_BRANCH'] = $serviceBranch;
$env['CI_PULL_REQUEST'] = $servicePullRequest;
$object = $this->collectJsonFile();
$same = $object->fillJobs($env);
self::assertSame($same, $object);
self::assertSame($repoToken, $object->getRepoToken());
self::assertSame($parallel, $object->getParallel());
self::assertSame($serviceName, $object->getServiceName());
self::assertSame($serviceNumber, $object->getServiceNumber());
self::assertSame($serviceBuildUrl, $object->getServiceBuildUrl());
self::assertSame($serviceBranch, $object->getServiceBranch());
self::assertSame($servicePullRequest, $object->getServicePullRequest());
}
/**
* @test
*/
public function shouldFillJobsForServiceEventType()
{
$repoToken = 'token';
$serviceName = 'php-coveralls';
$serviceEventType = 'manual';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = $repoToken;
$env['COVERALLS_RUN_LOCALLY'] = '1';
$env['COVERALLS_EVENT_TYPE'] = $serviceEventType;
$env['CI_NAME'] = $serviceName;
$object = $this->collectJsonFile();
$same = $object->fillJobs($env);
self::assertSame($same, $object);
self::assertSame($repoToken, $object->getRepoToken());
self::assertSame($serviceName, $object->getServiceName());
self::assertNull($object->getServiceJobId());
self::assertSame($serviceEventType, $object->getServiceEventType());
}
/**
* @test
*/
public function shouldFillJobsForGithubActions()
{
$repoToken = 'token';
$serviceName = 'github';
$serviceJobId = '1.1';
$env = [];
$env['CI_NAME'] = $serviceName;
$env['CI_JOB_ID'] = $serviceJobId;
$env['COVERALLS_REPO_TOKEN'] = $repoToken;
$object = $this->collectJsonFile();
$same = $object->fillJobs($env);
self::assertSame($same, $object);
self::assertSame($serviceName, $object->getServiceName());
self::assertSame($serviceJobId, $object->getServiceJobId());
}
/**
* @test
*/
public function shouldFillJobsForUnsupportedJob()
{
$repoToken = 'token';
$env = [];
$env['COVERALLS_REPO_TOKEN'] = $repoToken;
$object = $this->collectJsonFile();
$same = $object->fillJobs($env);
self::assertSame($same, $object);
self::assertSame($repoToken, $object->getRepoToken());
}
/**
* @test
*/
public function throwRuntimeExceptionOnFillingJobsIfInvalidEnv()
{
$this->expectException(\PhpCoveralls\Bundle\CoverallsBundle\Entity\Exception\RequirementsNotSatisfiedException::class);
$env = [];
$object = $this->collectJsonFile();
$object->fillJobs($env);
}
/**
* @test
*/
public function throwRuntimeExceptionOnFillingJobsWithoutSourceFiles()
{
$this->expectException(\RuntimeException::class);
$env = [];
$env['TRAVIS'] = true;
$env['TRAVIS_BUILD_NUMBER'] = '123';
$env['TRAVIS_JOB_ID'] = '1.1';
$object = $this->collectJsonFileWithoutSourceFiles();
$object->fillJobs($env);
}
// reportLineCoverage()
/**
* @test
*/
public function shouldReportLineCoverage()
{
$object = $this->collectJsonFile();
self::assertSame(50.0, $object->reportLineCoverage());
$metrics = $object->getMetrics();
self::assertSame(2, $metrics->getStatements());
self::assertSame(1, $metrics->getCoveredStatements());
self::assertSame(50.0, $metrics->getLineCoverage());
}
// excludeNoStatementsFiles()
/**
* @test
*/
public function shouldExcludeNoStatementsFiles()
{
$srcDir = $this->srcDir . \DIRECTORY_SEPARATOR;
$object = $this->collectJsonFile();
// before excluding
$sourceFiles = $object->getSourceFiles();
self::assertCount(4, $sourceFiles);
// filenames
$paths = array_keys($sourceFiles);
$filenames = array_map(function ($path) use ($srcDir) {return str_replace($srcDir, '', $path); }, $paths);
self::assertContains('test.php', $filenames);
self::assertContains('test2.php', $filenames);
self::assertContains('TestInterface.php', $filenames);
self::assertContains('AbstractClass.php', $filenames);
// after excluding
$object->excludeNoStatementsFiles();
$sourceFiles = $object->getSourceFiles();
self::assertCount(2, $sourceFiles);
// filenames
$paths = array_keys($sourceFiles);
$filenames = array_map(function ($path) use ($srcDir) {return str_replace($srcDir, '', $path); }, $paths);
self::assertContains('test.php', $filenames);
self::assertContains('test2.php', $filenames);
self::assertNotContains('TestInterface.php', $filenames);
self::assertNotContains('AbstractClass.php', $filenames);
}
protected function legacySetUp()
{
$this->setUpDir(realpath(__DIR__ . '/../../..'));
$this->object = new JsonFile();
}
/**
* @return SourceFile
*/
protected function createSourceFile()
{
$filename = 'test.php';
$path = $this->srcDir . \DIRECTORY_SEPARATOR . $filename;
return new SourceFile($path, $filename);
}
/**
* @return string
*/
protected function getCloverXml()
{
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1365848893">
<project timestamp="1365848893">
<file name="%s/test.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="1"/>
</file>
<file name="%s/TestInterface.php">
<class name="TestInterface" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="0" coveredstatements="0" elements="1" coveredelements="0"/>
</class>
<line num="5" type="method" name="hello" crap="1" count="0"/>
</file>
<file name="%s/AbstractClass.php">
<class name="AbstractClass" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="0" coveredstatements="0" elements="1" coveredelements="0"/>
</class>
<line num="5" type="method" name="hello" crap="1" count="0"/>
</file>
<file name="dummy.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="0"/>
</file>
<package name="Hoge">
<file name="%s/test2.php">
<class name="TestFile" namespace="Hoge">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="6" type="method" name="__construct" crap="1" count="0"/>
<line num="8" type="stmt" count="0"/>
</file>
</package>
</project>
</coverage>
XML;
return sprintf($xml, $this->srcDir, $this->srcDir, $this->srcDir, $this->srcDir);
}
/**
* @return \SimpleXMLElement
*/
protected function createCloverXml()
{
$xml = $this->getCloverXml();
return simplexml_load_string($xml);
}
/**
* @return JsonFile
*/
protected function collectJsonFile()
{
$xml = $this->createCloverXml();
$collector = new CloverXmlCoverageCollector();
return $collector->collect($xml, $this->srcDir);
}
/**
* @return string
*/
protected function getNoSourceCloverXml()
{
return <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1365848893">
<project timestamp="1365848893">
<file name="dummy.php">
<class name="TestFile" namespace="global">
<metrics methods="1" coveredmethods="0" conditionals="0" coveredconditionals="0" statements="1" coveredstatements="0" elements="2" coveredelements="0"/>
</class>
<line num="5" type="method" name="__construct" crap="1" count="0"/>
<line num="7" type="stmt" count="0"/>
</file>
</project>
</coverage>
XML;
}
/**
* @return \SimpleXMLElement
*/
protected function createNoSourceCloverXml()
{
$xml = $this->getNoSourceCloverXml();
return simplexml_load_string($xml);
}
/**
* @return JsonFile
*/
protected function collectJsonFileWithoutSourceFiles()
{
$xml = $this->createNoSourceCloverXml();
$collector = new CloverXmlCoverageCollector();
return $collector->collect($xml, $this->srcDir);
}
}
@@ -0,0 +1,139 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Entity;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Metrics;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Metrics
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class MetricsTest extends ProjectTestCase
{
/**
* @var array
*/
private $coverage;
// hasStatements()
// getStatements()
/**
* @test
*/
public function shouldNotHaveStatementsOnConstructionWithoutCoverage()
{
$object = new Metrics();
self::assertFalse($object->hasStatements());
self::assertSame(0, $object->getStatements());
}
/**
* @test
*/
public function shouldHaveStatementsOnConstructionWithCoverage()
{
$object = new Metrics($this->coverage);
self::assertTrue($object->hasStatements());
self::assertSame(3, $object->getStatements());
}
// getCoveredStatements()
/**
* @test
*/
public function shouldNotHaveCoveredStatementsOnConstructionWithoutCoverage()
{
$object = new Metrics();
self::assertSame(0, $object->getCoveredStatements());
}
/**
* @test
*/
public function shouldHaveCoveredStatementsOnConstructionWithCoverage()
{
$object = new Metrics($this->coverage);
self::assertSame(2, $object->getCoveredStatements());
}
// getLineCoverage()
/**
* @test
*/
public function shouldNotHaveLineCoverageOnConstructionWithoutCoverage()
{
$object = new Metrics();
self::assertSame(0, $object->getLineCoverage());
}
/**
* @test
*/
public function shouldHaveLineCoverageOnConstructionWithCoverage()
{
$object = new Metrics($this->coverage);
$this->assertWithDelta(200 / 3, $object->getLineCoverage(), 0.0000000000001);
}
// merge()
/**
* @test
*/
public function shouldMergeThatWithEmptyMetrics()
{
$object = new Metrics();
$that = new Metrics($this->coverage);
$object->merge($that);
self::assertSame(3, $object->getStatements());
self::assertSame(2, $object->getCoveredStatements());
$this->assertWithDelta(200 / 3, $object->getLineCoverage(), 0.0000000000001);
}
/**
* @test
*/
public function shouldMergeThat()
{
$object = new Metrics($this->coverage);
$that = new Metrics($this->coverage);
$object->merge($that);
self::assertSame(6, $object->getStatements());
self::assertSame(4, $object->getCoveredStatements());
$this->assertWithDelta(400 / 6, $object->getLineCoverage(), 0.0000000000001);
}
protected function legacySetUp()
{
$this->coverage = array_fill(0, 5, null);
$this->coverage[1] = 1;
$this->coverage[3] = 1;
$this->coverage[4] = 0;
}
private function assertWithDelta($expected, $actual, $delta, $message = '')
{
if (method_exists(ProjectTestCase::class, 'assertEqualsWithDelta')) {
parent::assertEqualsWithDelta($expected, $actual, $delta, $message);
return;
}
self::assertEquals($expected, $actual, $message, $delta);
}
}
@@ -0,0 +1,154 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Entity;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\Coveralls
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class SourceFileTest extends ProjectTestCase
{
/**
* @var string
*/
private $path;
/**
* @var SourceFile
*/
private $object;
// getName()
/**
* @test
*/
public function shouldHaveNameOnConstruction()
{
self::assertSame($this->filename, $this->object->getName());
}
// getSource()
/**
* @test
*/
public function shouldHaveSourceOnConstruction()
{
$expected = trim(file_get_contents($this->path));
self::assertSame($expected, $this->object->getSource());
}
// getCoverage()
/**
* @test
*/
public function shouldHaveNullCoverageOnConstruction()
{
$expected = array_fill(0, 9, null);
self::assertSame($expected, $this->object->getCoverage());
}
// getPath()
/**
* @test
*/
public function shouldHavePathOnConstruction()
{
$this->assertSamePath($this->path, $this->object->getPath());
}
// getFileLines()
/**
* @test
*/
public function shouldHaveFileLinesOnConstruction()
{
self::assertSame(9, $this->object->getFileLines());
}
// toArray()
/**
* @test
*/
public function shouldConvertToArray()
{
$expected = [
'name' => $this->filename,
'source' => trim(file_get_contents($this->path)),
'coverage' => array_fill(0, 9, null),
];
self::assertSame($expected, $this->object->toArray());
self::assertSame(json_encode($expected), (string) $this->object);
}
// addCoverage()
/**
* @test
*/
public function shouldAddCoverage()
{
$this->object->addCoverage(5, 1);
$expected = array_fill(0, 9, null);
$expected[5] = 1;
self::assertSame($expected, $this->object->getCoverage());
}
// getMetrics()
// reportLineCoverage()
/**
* @test
*/
public function shouldReportLineCoverage0PercentWithoutAddingCoverage()
{
$metrics = $this->object->getMetrics();
self::assertSame(0, $metrics->getStatements());
self::assertSame(0, $metrics->getCoveredStatements());
self::assertSame(0, $metrics->getLineCoverage());
self::assertSame(0, $this->object->reportLineCoverage());
}
/**
* @test
*/
public function shouldReportLineCoverage100PercentAfterAddingCoverage()
{
$this->object->addCoverage(6, 1);
$metrics = $this->object->getMetrics();
self::assertSame(1, $metrics->getStatements());
self::assertSame(1, $metrics->getCoveredStatements());
self::assertSame(100, $metrics->getLineCoverage());
self::assertSame(100, $this->object->reportLineCoverage());
}
protected function legacySetUp()
{
$this->setUpDir(realpath(__DIR__ . '/../../..'));
$this->filename = 'test.php';
$this->path = $this->srcDir . \DIRECTORY_SEPARATOR . $this->filename;
$this->object = new SourceFile($this->path, $this->filename);
}
}
@@ -0,0 +1,459 @@
<?php
namespace PhpCoveralls\Tests\Bundle\CoverallsBundle\Repository;
use PhpCoveralls\Bundle\CoverallsBundle\Api\Jobs;
use PhpCoveralls\Bundle\CoverallsBundle\Config\Configuration;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Exception\RequirementsNotSatisfiedException;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\JsonFile;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\Metrics;
use PhpCoveralls\Bundle\CoverallsBundle\Entity\SourceFile;
use PhpCoveralls\Bundle\CoverallsBundle\Repository\JobsRepository;
use PhpCoveralls\Tests\ProjectTestCase;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
/**
* @covers \PhpCoveralls\Bundle\CoverallsBundle\Repository\JobsRepository
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class JobsRepositoryTest extends ProjectTestCase
{
// persist()
/**
* @test
*/
public function shouldPersist()
{
$statusCode = 200;
$url = 'https://coveralls.io/jobs/67528';
$response = new \GuzzleHttp\Psr7\Response(
$statusCode,
[],
json_encode([
'message' => 'Job #115.3',
'url' => $url,
]),
'1.1',
'OK'
);
$api = $this->createApiMock($response, $statusCode, $url);
$config = $this->createConfiguration();
$logger = $this->createLoggerMock();
$object = new JobsRepository($api, $config);
$object->setLogger($logger);
self::assertTrue($object->persist());
}
/**
* @test
*/
public function shouldPersistDryRun()
{
$api = $this->createApiMock(null, 200); // params means API won't crash, but it will return null, as it does for dry-run
$config = $this->createConfiguration();
$logger = $this->createLoggerMock();
$object = new JobsRepository($api, $config);
$object->setLogger($logger);
self::assertTrue($object->persist());
}
// unexpected Exception
// source files not found
/**
* @test
*/
public function unexpectedException()
{
$api = $this->createApiMockWithException();
$config = $this->createConfiguration();
$logger = $this->createLoggerMock();
$object = new JobsRepository($api, $config);
$object->setLogger($logger);
self::assertFalse($object->persist());
}
/**
* @test
*/
public function requirementsNotSatisfiedException()
{
$api = $this->createApiMockWithRequirementsNotSatisfiedException();
$config = $this->createConfiguration();
$logger = $this->createLoggerMock();
$object = new JobsRepository($api, $config);
$object->setLogger($logger);
self::assertFalse($object->persist());
}
// curl error
/**
* @test
*/
public function networkDisconnected()
{
$api = $this->createApiMock(null, null);
$config = $this->createConfiguration();
$logger = $this->createLoggerMock();
$object = new JobsRepository($api, $config);
$object->setLogger($logger);
self::assertFalse($object->persist());
}
// response 422
/**
* @test
*/
public function response422()
{
$statusCode = 422;
$response = new \GuzzleHttp\Psr7\Response(
$statusCode,
[],
json_encode([
'message' => 'Build processing error.',
'url' => '',
'error' => true,
]),
'1.1',
'Unprocessable Entity'
);
$api = $this->createApiMock($response, $statusCode);
$config = $this->createConfiguration();
$logger = $this->createLoggerMock();
$object = new JobsRepository($api, $config);
$object->setLogger($logger);
self::assertFalse($object->persist());
}
// response 500
/**
* @test
*/
public function response500()
{
$statusCode = 500;
$response = new \GuzzleHttp\Psr7\Response($statusCode, [], null, '1.1', 'Internal Server Error');
$api = $this->createApiMock($response, $statusCode);
$config = $this->createConfiguration();
$logger = $this->createLoggerMock();
$object = new JobsRepository($api, $config);
$object->setLogger($logger);
self::assertFalse($object->persist());
}
protected function legacySetUp()
{
$this->setUpDir(realpath(__DIR__ . '/../../..'));
}
/**
* @return Jobs
*/
protected function createApiMockWithRequirementsNotSatisfiedException()
{
$api = $this->prophesize(Jobs::class);
$this->setUpJobsApiWithCollectCloverXmlThrow($api, new RequirementsNotSatisfiedException());
$this->setUpJobsApiWithGetJsonFileNotCalled($api);
$this->setUpJobsApiWithCollectGitInfoNotCalled($api);
$this->setUpJobsApiWithCollectEnvVarsNotCalled($api);
$this->setUpJobsApiWithDumpJsonFileNotCalled($api);
$this->setUpJobsApiWithSendNotCalled($api);
return $api->reveal();
}
/**
* @return Jobs
*/
protected function createApiMockWithException()
{
$api = $this->prophesize(Jobs::class);
$this->setUpJobsApiWithCollectCloverXmlThrow($api, new \Exception('unexpected exception'));
$this->setUpJobsApiWithGetJsonFileNotCalled($api);
$this->setUpJobsApiWithCollectGitInfoNotCalled($api);
$this->setUpJobsApiWithCollectEnvVarsNotCalled($api);
$this->setUpJobsApiWithDumpJsonFileNotCalled($api);
$this->setUpJobsApiWithSendNotCalled($api);
return $api->reveal();
}
/**
* @param null|ResponseInterface $response
* @param null|int $statusCode
* @param string $uri
*
* @return Jobs
*/
protected function createApiMock($response, $statusCode = null, $uri = '/')
{
$api = $this->prophesize(Jobs::class);
$this->setUpJobsApiWithCollectCloverXmlCalled($api);
$this->setUpJobsApiWithGetJsonFileCalled($api, $this->createJsonFile());
$this->setUpJobsApiWithCollectGitInfoCalled($api);
$this->setUpJobsApiWithCollectEnvVarsCalled($api);
$this->setUpJobsApiWithDumpJsonFileCalled($api);
$this->setUpJobsApiWithSendCalled($api, $statusCode, new \GuzzleHttp\Psr7\Request('POST', $uri), $response);
return $api->reveal();
}
/**
* @return LoggerInterface
*/
protected function createLoggerMock()
{
$logger = $this->prophesize(NullLogger::class);
$logger->info();
$logger->error();
return $logger->reveal();
}
// dependent object
/**
* @param int $percent
*
* @return array
*/
protected function createCoverage($percent)
{
// percent = (covered / stmt) * 100;
// (percent * stmt) / 100 = covered
$stmt = 100;
$covered = ($percent * $stmt) / 100;
$coverage = array_fill(0, 100, 0);
for ($i = 0; $i < $covered; ++$i) {
$coverage[$i] = 1;
}
return $coverage;
}
protected function createJsonFile()
{
$jsonFile = new JsonFile();
$repositoryTestDir = $this->srcDir . '/RepositoryTest';
$sourceFiles = [
0 => new SourceFile($repositoryTestDir . '/Coverage0.php', 'Coverage0.php'),
10 => new SourceFile($repositoryTestDir . '/Coverage10.php', 'Coverage10.php'),
70 => new SourceFile($repositoryTestDir . '/Coverage70.php', 'Coverage70.php'),
80 => new SourceFile($repositoryTestDir . '/Coverage80.php', 'Coverage80.php'),
90 => new SourceFile($repositoryTestDir . '/Coverage90.php', 'Coverage90.php'),
100 => new SourceFile($repositoryTestDir . '/Coverage100.php', 'Coverage100.php'),
];
foreach ($sourceFiles as $percent => $sourceFile) {
$sourceFile->getMetrics()->merge(new Metrics($this->createCoverage($percent)));
$jsonFile->addSourceFile($sourceFile);
}
return $jsonFile;
}
protected function createConfiguration()
{
$config = new Configuration();
return $config->addCloverXmlPath($this->cloverXmlPath);
}
// mock
/**
* @param Jobs $api
*/
private function setUpJobsApiWithCollectCloverXmlCalled($api)
{
$api
->collectCloverXml()
->will(function () {
return $this;
})
->shouldBeCalled()
;
}
/**
* @param Jobs $api
* @param \Exception $exception
*/
private function setUpJobsApiWithCollectCloverXmlThrow($api, $exception)
{
$api
->collectCloverXml()
->willThrow($exception)
->shouldBeCalled()
;
}
/**
* @param Jobs $api
* @param JsonFile $jsonFile
*/
private function setUpJobsApiWithGetJsonFileCalled($api, $jsonFile)
{
$api
->getJsonFile()
->willReturn($jsonFile)
->shouldBeCalled()
;
}
/**
* @param Jobs $api
*/
private function setUpJobsApiWithGetJsonFileNotCalled($api)
{
$api
->getJsonFile()
->shouldNotBeCalled()
;
}
/**
* @param Jobs $api
*/
private function setUpJobsApiWithCollectGitInfoCalled($api)
{
$api
->collectGitInfo()
->will(function () {
return $this;
})
->shouldBeCalled()
;
}
/**
* @param Jobs $api
*/
private function setUpJobsApiWithCollectGitInfoNotCalled($api)
{
$api
->collectGitInfo()
->shouldNotBeCalled()
;
}
/**
* @param Jobs $api
*/
private function setUpJobsApiWithCollectEnvVarsCalled($api)
{
$api
->collectEnvVars($_SERVER)
->will(function () {
return $this;
})
->shouldBeCalled()
;
}
/**
* @param Jobs $api
*/
private function setUpJobsApiWithCollectEnvVarsNotCalled($api)
{
$api
->collectEnvVars()
->shouldNotBeCalled()
;
}
/**
* @param Jobs $api
*/
private function setUpJobsApiWithDumpJsonFileCalled($api)
{
$api
->dumpJsonFile()
->will(function () {
return $this;
})
->shouldBeCalled()
;
}
/**
* @param Jobs $api
*/
private function setUpJobsApiWithDumpJsonFileNotCalled($api)
{
$api
->dumpJsonFile()
->shouldNotBeCalled()
;
}
/**
* @param Jobs $api
* @param null|int $statusCode
* @param RequestInterface $request
* @param null|ResponseInterface $response
*/
private function setUpJobsApiWithSendCalled($api, $statusCode, $request, $response)
{
if ($statusCode === 200) {
$api
->send()
->willReturn($response)
->shouldBeCalled()
;
} else {
if ($statusCode === null) {
$exception = new \GuzzleHttp\Exception\ConnectException('Connection refused', $request);
} elseif ($statusCode === 422) {
$exception = \GuzzleHttp\Exception\ClientException::create($request, $response);
} else {
$exception = \GuzzleHttp\Exception\ServerException::create($request, $response);
}
$api
->send()
->willThrow($exception)
->shouldBeCalled()
;
}
}
/**
* @param Jobs $api
*/
private function setUpJobsApiWithSendNotCalled($api)
{
$api
->send()
->shouldNotBeCalled()
;
}
}
@@ -0,0 +1,507 @@
<?php
namespace PhpCoveralls\Tests\Component\File;
use PhpCoveralls\Component\File\Path;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Component\File\Path
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class PathTest extends ProjectTestCase
{
/**
* @var string
*/
private $existingFile;
/**
* @var string
*/
private $unreadablePath;
/**
* @var string
*/
private $unwritablePath;
/**
* @var string
*/
private $unwritableDir;
/**
* @var Path
*/
private $object;
// provider
/**
* @return array
*/
public function provideRelativePaths()
{
return [
[''],
['.'],
['..'],
];
}
/**
* @return array
*/
public function provideShouldNotBeRelativePathCases()
{
if ($this->isWindowsOS()) {
return [
['c:\\'],
['z:\\path\\to\\somewhere'],
];
}
return [
['/'],
['/path/to/somewhere'],
];
}
// isRelativePath()
/**
* @test
*
* @dataProvider provideRelativePaths
*
* @param mixed $path
*/
public function shouldBeRelativePath($path)
{
self::assertTrue($this->object->isRelativePath($path));
}
/**
* @test
*
* @dataProvider provideShouldNotBeRelativePathCases
*
* @param mixed $path
*/
public function shouldNotBeRelativePath($path)
{
self::assertFalse($this->object->isRelativePath($path));
}
// toAbsolutePath()
/**
* @test
*/
public function shouldNotConvertAbsolutePath()
{
$path = false;
$rootDir = __DIR__;
self::assertFalse($this->object->toAbsolutePath($path, $rootDir));
}
/**
* @test
*
* @dataProvider provideRelativePaths
*
* @param mixed $path
*/
public function shouldConvertAbsolutePathIfRelativePathGiven($path)
{
$rootDir = '/path/to/dir';
$expected = $rootDir . \DIRECTORY_SEPARATOR . $path;
self::assertSame($expected, $this->object->toAbsolutePath($path, $rootDir));
}
/**
* @test
*/
public function shouldConvertAbsolutePathIfAbsolutePathGiven()
{
$rootDir = '/path/to/dir';
$path = __DIR__;
$expected = $path;
self::assertSame($expected, $this->object->toAbsolutePath($path, $rootDir));
}
// getRealPath()
/**
* @test
*/
public function shouldNotBeRealPath()
{
$path = false;
$rootDir = __DIR__;
self::assertFalse($this->object->getRealPath($path, $rootDir));
}
/**
* @test
*
* @dataProvider provideRelativePaths
*
* @param mixed $path
*/
public function shouldGetRealPathIfRelativePathGiven($path)
{
$rootDir = __DIR__;
$expected = realpath($rootDir . \DIRECTORY_SEPARATOR . $path);
self::assertSame($expected, $this->object->getRealPath($path, $rootDir));
}
/**
* @test
*/
public function shouldGetRealPathIfAbsolutePathGiven()
{
$path = __DIR__;
$rootDir = '';
$expected = realpath($path);
self::assertSame($expected, $this->object->getRealPath($path, $rootDir));
}
// getRealDir()
/**
* @test
*/
public function shouldNotBeRealDir()
{
$path = false;
$rootDir = __DIR__;
self::assertFalse($this->object->getRealDir($path, $rootDir));
}
/**
* @test
*/
public function shouldGetRealDirIfRelativePathGiven()
{
$path = '';
$rootDir = __DIR__;
$expected = realpath($rootDir . \DIRECTORY_SEPARATOR . $path);
self::assertSame($expected, $this->object->getRealDir($path, $rootDir));
}
/**
* @test
*/
public function shouldGetRealDirIfAbsolutePathGiven()
{
$path = __DIR__;
$rootDir = '';
$expected = realpath($path . '/..');
self::assertSame($expected, $this->object->getRealDir($path, $rootDir));
}
// getRealWritingFilePath()
/**
* @test
*/
public function shouldNotBeRealWritingFilePath()
{
$path = false;
$rootDir = __DIR__;
self::assertFalse($this->object->getRealWritingFilePath($path, $rootDir));
}
/**
* @test
*/
public function shouldGetRealWritingPathIfRelativePathGiven()
{
$path = 'test.txt';
$rootDir = __DIR__;
$expected = $rootDir . \DIRECTORY_SEPARATOR . $path;
self::assertSame($expected, $this->object->getRealWritingFilePath($path, $rootDir));
}
// isRealPathExist()
/**
* @test
*/
public function shouldNotExistRealPathIfFalseGiven()
{
$path = false;
self::assertFalse($this->object->isRealPathExist($path));
}
/**
* @test
*/
public function shouldNotExistRealPath()
{
$path = __DIR__ . '/dummy.dir';
self::assertFalse($this->object->isRealPathExist($path));
}
/**
* @test
*/
public function shouldExistRealPath()
{
touch($this->existingFile);
self::assertTrue($this->object->isRealPathExist($this->existingFile));
}
// isRealFileExist()
/**
* @test
*/
public function shouldNotExistRealFile()
{
$path = __DIR__ . '/dummy.file';
self::assertFalse($this->object->isRealFileExist($path));
}
/**
* @test
*/
public function shouldNotExistRealFileIfDirGiven()
{
$path = __DIR__;
self::assertFalse($this->object->isRealFileExist($path));
}
/**
* @test
*/
public function shouldExistRealFile()
{
touch($this->existingFile);
self::assertTrue($this->object->isRealFileExist($this->existingFile));
}
// isRealFileReadable()
/**
* @test
*/
public function shouldNotBeRealFileReadableIfFileNotFound()
{
$path = __DIR__ . '/dummy.file';
self::assertFalse($this->object->isRealFileReadable($path));
}
/**
* @test
*/
public function shouldNotBeRealFileReadableIfFileUnreadable()
{
if ($this->isWindowsOS()) {
// On Windows there is no write-only attribute.
self::markTestSkipped('Unable to run on Windows');
}
$this->touchUnreadableFile();
self::assertFalse($this->object->isRealFileReadable($this->unreadablePath));
}
/**
* @test
*/
public function shouldBeRealFileReadable()
{
touch($this->existingFile);
self::assertTrue($this->object->isRealFileReadable($this->existingFile));
}
// isRealFileWritable()
/**
* @test
*/
public function shouldNotBeRealFileWritableIfFileNotFound()
{
$path = __DIR__ . '/dummy.file';
self::assertFalse($this->object->isRealFileWritable($path));
}
/**
* @test
*/
public function shouldNotBeRealFileWritableIfFileUnwritable()
{
$this->touchUnwritableFile();
self::assertFalse($this->object->isRealFileWritable($this->unwritablePath));
}
/**
* @test
*/
public function shouldBeRealFileWritable()
{
touch($this->existingFile);
self::assertTrue($this->object->isRealFileWritable($this->existingFile));
}
// isRealDirExist()
/**
* @test
*/
public function shouldNotExistRealDir()
{
$path = __DIR__ . '/dummy.dir';
self::assertFalse($this->object->isRealDirExist($path));
}
/**
* @test
*/
public function shouldNotExistRealDirIfFileGiven()
{
touch($this->existingFile);
self::assertFalse($this->object->isRealDirExist($this->existingFile));
}
/**
* @test
*/
public function shouldExistRealDir()
{
$path = __DIR__;
self::assertTrue($this->object->isRealDirExist($path));
}
// isRealDirWritable()
/**
* @test
*/
public function shouldNotBeRealDirWritableIfDirNotFound()
{
$path = __DIR__ . '/dummy.dir';
self::assertFalse($this->object->isRealDirWritable($path));
}
/**
* @test
*/
public function shouldNotBeRealDirWritableIfDirUnwritable()
{
if ($this->isWindowsOS()) {
// On Windows read-only attribute on dir applies to files in dir, but not the dir itself.
self::markTestSkipped('Unable to run on Windows');
}
$this->mkdirUnwritableDir();
self::assertFalse($this->object->isRealDirWritable($this->unwritableDir));
}
/**
* @test
*/
public function shouldBeRealDirWritable()
{
$path = __DIR__;
self::assertTrue($this->object->isRealDirWritable($path));
}
protected function legacySetUp()
{
$this->existingFile = __DIR__ . '/existing.txt';
$this->unreadablePath = __DIR__ . '/unreadable.txt';
$this->unwritablePath = __DIR__ . '/unwritable.txt';
$this->unwritableDir = __DIR__ . '/unwritable.dir';
$this->object = new Path();
}
protected function legacyTearDown()
{
$this->rmFile($this->existingFile);
$this->rmFile($this->unreadablePath);
$this->rmFile($this->unwritablePath);
$this->rmDir($this->unwritableDir);
}
protected function touchUnreadableFile()
{
$this->rmFile($this->unreadablePath);
touch($this->unreadablePath);
chmod($this->unreadablePath, 0377);
}
protected function touchUnwritableFile()
{
$this->rmFile($this->unwritablePath);
touch($this->unwritablePath);
chmod($this->unwritablePath, 0577);
}
protected function mkdirUnwritableDir()
{
$this->rmDir($this->unwritableDir);
mkdir($this->unwritableDir);
chmod($this->unwritableDir, 0577);
}
/**
* @return bool
*/
private function isWindowsOS()
{
static $isWindows;
if ($isWindows === null) {
$isWindows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
}
return $isWindows;
}
}
@@ -0,0 +1,46 @@
<?php
namespace PhpCoveralls\Tests\Component\Log;
use PhpCoveralls\Component\Log\ConsoleLogger;
use PhpCoveralls\Tests\ProjectTestCase;
use Symfony\Component\Console\Output\StreamOutput;
/**
* @covers \PhpCoveralls\Component\Log\ConsoleLogger
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class ConsoleLoggerTest extends ProjectTestCase
{
/**
* @test
*/
public function shouldWritelnToOutput()
{
$message = 'log test message';
$output = $this->createAdapterMockWith($message);
$object = new ConsoleLogger($output);
$object->log('info', $message);
}
/**
* @param string $message
*
* @return StreamOutput
*/
protected function createAdapterMockWith($message)
{
$mock = $this->prophesize(StreamOutput::class);
$mock
->writeln($message)
->shouldBeCalled()
;
return $mock->reveal();
}
}
@@ -0,0 +1,54 @@
<?php
namespace PhpCoveralls\Tests\Component\System\Git;
use PhpCoveralls\Component\System\Git\GitCommand;
use PhpCoveralls\Tests\ProjectTestCase;
/**
* @covers \PhpCoveralls\Component\System\Git\GitCommand
* @covers \PhpCoveralls\Component\System\SystemCommandExecutor
*
* @author Kitamura Satoshi <with.no.parachute@gmail.com>
*
* @internal
*/
final class GitCommandTest extends ProjectTestCase
{
/**
* @test
*/
public function shouldReturnBranches()
{
$object = new GitCommand();
$actual = $object->getBranches();
self::assertIsArray($actual);
self::assertNotEmpty($actual);
}
/**
* @test
*/
public function shouldReturnHeadCommit()
{
$object = new GitCommand();
$actual = $object->getHeadCommit();
self::assertIsArray($actual);
self::assertNotEmpty($actual);
self::assertCount(6, $actual);
}
/**
* @test
*/
public function shouldReturnRemotes()
{
$object = new GitCommand();
$actual = $object->getRemotes();
self::assertIsArray($actual);
self::assertNotEmpty($actual);
}
}
@@ -0,0 +1 @@
# empty config
@@ -0,0 +1,6 @@
<?php
abstract class AbstractClass
{
abstract public function hello();
}
@@ -0,0 +1,10 @@
<?php
namespace CoverallsTest;
class Coverage0
{
public function doSomething()
{
$var = '';
}
}
@@ -0,0 +1,10 @@
<?php
namespace CoverallsTest;
class Coverage10
{
public function doSomething()
{
$var = '';
}
}
@@ -0,0 +1,10 @@
<?php
namespace CoverallsTest;
class Coverage100
{
public function doSomething()
{
$var = '';
}
}
@@ -0,0 +1,10 @@
<?php
namespace CoverallsTest;
class Coverage70
{
public function doSomething()
{
$var = '';
}
}
@@ -0,0 +1,10 @@
<?php
namespace CoverallsTest;
class Coverage80
{
public function doSomething()
{
$var = '';
}
}
@@ -0,0 +1,10 @@
<?php
namespace CoverallsTest;
class Coverage90
{
public function doSomething()
{
$var = '';
}
}
@@ -0,0 +1,6 @@
<?php
interface TestInterface
{
public function hello();
}
@@ -0,0 +1,9 @@
<?php
class TestFile
{
public function __construct()
{
$this->message = 'hoge';
}
}
@@ -0,0 +1,10 @@
<?php
namespace Hoge;
class TestFile
{
public function __construct()
{
$this->message = 'hoge';
}
}
@@ -0,0 +1,10 @@
<?php
namespace Hoge;
class TestFileBis
{
public function __construct()
{
$this->message = 'hoge';
}
}
@@ -0,0 +1,212 @@
<?php
namespace PhpCoveralls\Tests;
use LegacyPHPUnit\TestCase;
abstract class ProjectTestCase extends TestCase
{
/**
* @var string
*/
protected $rootDir;
/**
* @var string
*/
protected $srcDir;
/**
* @var string
*/
protected $url;
/**
* @var string
*/
protected $filename;
/**
* @var string
*/
protected $buildDir;
/**
* @var string
*/
protected $logsDir;
/**
* @var string
*/
protected $cloverXmlPath;
/**
* @var string
*/
protected $cloverXmlPath1;
/**
* @var string
*/
protected $cloverXmlPath2;
/**
* @var string
*/
protected $jsonPath;
public function __call($method, $args)
{
switch ($method) {
case 'assertStringContainsString':
\call_user_func_array([$this, 'assertContains'], $args);
break;
case 'assertIsArray':
\call_user_func_array([$this, 'assertInternalType'], array_merge(['array'], $args));
break;
case 'expectException':
\call_user_func_array([$this, 'setExpectedException'], $args);
break;
default:
trigger_error("Call to undefined method ::$method", E_USER_ERROR);
}
}
/**
* @param string $projectDir
*/
protected function setUpDir($projectDir)
{
$this->rootDir = realpath($projectDir . '/Fixture');
$this->srcDir = realpath($this->rootDir . '/files');
$this->url = 'https://coveralls.io/api/v1/jobs';
$this->filename = 'json_file';
// build
$this->buildDir = $this->rootDir . '/build';
$this->logsDir = $this->rootDir . '/build/logs';
// log
$this->cloverXmlPath = $this->logsDir . '/clover.xml';
$this->cloverXmlPath1 = $this->logsDir . '/clover-part1.xml';
$this->cloverXmlPath2 = $this->logsDir . '/clover-part2.xml';
$this->jsonPath = $this->logsDir . '/coveralls-upload.json';
}
/**
* @param string $srcDir
* @param string $logsDir
* @param string|string[] $cloverXmlPaths
* @param bool $logsDirUnwritable
* @param bool $jsonPathUnwritable
*/
protected function makeProjectDir($srcDir = null, $logsDir = null, $cloverXmlPaths = null, $logsDirUnwritable = false, $jsonPathUnwritable = false)
{
if ($srcDir !== null && !is_dir($srcDir)) {
mkdir($srcDir, 0777, true);
}
if ($logsDir !== null && !is_dir($logsDir)) {
mkdir($logsDir, 0777, true);
}
if ($cloverXmlPaths !== null) {
if (\is_array($cloverXmlPaths)) {
foreach ($cloverXmlPaths as $cloverXmlPath) {
touch($cloverXmlPath);
}
} else {
touch($cloverXmlPaths);
}
}
if ($logsDirUnwritable && file_exists($logsDir)) {
chmod($logsDir, 0577);
}
if ($jsonPathUnwritable) {
touch($this->jsonPath);
chmod($this->jsonPath, 0577);
}
}
/**
* @param string $file
*/
protected function rmFile($file)
{
if (is_file($file)) {
// we try to unlock file, for that, we might need different permissions:
chmod(\dirname($file), 0777); // on unix
chmod($file, 0777); // on Windows
unlink($file);
}
}
/**
* @param string $dir
*/
protected function rmDir($dir)
{
if (is_dir($dir)) {
chmod($dir, 0777);
rmdir($dir);
}
}
/**
* @param string $path
*
* @return string
*/
protected function normalizePath($path)
{
return strtr(\DIRECTORY_SEPARATOR, '/', $path);
}
/**
* @param string $expected
* @param string $input
* @param null|string $msg
*/
protected function assertSamePath($expected, $input, $msg = null)
{
if ($msg !== null) {
self::assertSame(
$this->normalizePath($expected),
$this->normalizePath($input),
$msg
);
} else {
self::assertSame(
$this->normalizePath($expected),
$this->normalizePath($input)
);
}
}
/**
* @param string[] $expected
* @param string[] $input
* @param null|string $msg
*/
protected function assertSamePaths(array $expected, array $input, $msg = null)
{
$expected = array_map(function ($path) { return $this->normalizePath($path); }, $expected);
$input = array_map(function ($path) { return $this->normalizePath($path); }, $input);
if ($msg !== null) {
self::assertSame($expected, $input, $msg);
} else {
self::assertSame($expected, $input);
}
}
}